Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • geocontrib/geocontrib-frontend
  • ext_matthieu/geocontrib-frontend
  • fnecas/geocontrib-frontend
  • MatthieuE/geocontrib-frontend
4 results
Show changes
Commits on Source (35)
Showing
with 1251 additions and 626 deletions
{
"name": "geocontrib-frontend",
"version": "3.0.1",
"version": "3.0.2",
"private": true,
"scripts": {
"serve": "npm run init-proxy & npm run init-serve",
......@@ -16,7 +16,9 @@
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/vue-fontawesome": "^2.0.6",
"@mapbox/vector-tile": "^1.3.1",
"@turf/bbox": "^6.5.0",
"@turf/flip": "^6.5.0",
"@turf/helpers": "^6.5.0",
"axios": "^0.21.1",
"core-js": "^3.20.2",
"leaflet": "^1.7.1",
......@@ -33,13 +35,13 @@
"vuex": "^3.6.2"
},
"devDependencies": {
"@babel/eslint-parser": "^7.15.8",
"@fortawesome/fontawesome-free": "^5.15.4",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "^5.0.0-beta.6",
"@vue/cli-plugin-pwa": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@babel/eslint-parser": "^7.15.8",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^7.20.0",
"less": "^3.0.4",
......
......@@ -141,7 +141,7 @@
</div>
</div>
</div>
<div class="desktop flex push-right-desktop item title">
<div class="desktop flex push-right-desktop item title abstract">
<span>
{{ APPLICATION_ABSTRACT }}
</span>
......@@ -405,7 +405,13 @@ footer {
z-index: 1001;
}
@media screen and (min-width: 560px) {
@media screen and (max-width: 985px) {
.abstract{
display: none !important;
}
}
@media screen and (min-width: 590px) {
.mobile {
display: none !important;
}
......@@ -420,7 +426,7 @@ footer {
}
}
@media screen and (max-width: 560px) {
@media screen and (max-width: 590px) {
.desktop {
display: none !important;
}
......
......@@ -105,9 +105,9 @@ L.TileLayer.BetterWMS = L.TileLayer.WMS.extend({
if (err) {
content = `
<h4>${this.options.title}</h4>
<p>Données de la couche inaccessibles</p>
`;
<h4>${this.options.title}</h4>
<p>Données de la couche inaccessibles</p>
`;
L.popup({ maxWidth: 800 })
.setLatLng(latlng)
......@@ -156,15 +156,14 @@ const mapUtil = {
zoom,
zoomControl = true,
} = options;
map = L.map(el, {
maxZoom: 18,
minZoom: 1,
zoomControl: false,
}).setView(
[
!lat ? mapDefaultViewCenter[0] : lat,
!lng ? mapDefaultViewCenter[1] : lng,
lat ? lat : mapDefaultViewCenter[0],
lng ? lng : mapDefaultViewCenter[1],
],
!zoom ? mapDefaultViewZoom : zoom
);
......@@ -295,9 +294,8 @@ const mapUtil = {
const currentValue = properties[colorsStyle.custom_field_name];
const colorStyle = colorsStyle.colors[currentValue];
return colorStyle ? colorStyle : featureType.color;
} else {
return featureType.color;
}
return featureType.color;
},
addVectorTileLayer: function (url, project_slug, featureTypes, form_filters) {
......@@ -370,7 +368,7 @@ const mapUtil = {
window.layerMVT = layerMVT;
},
addFeatures: function (features, filter, addToMap = true, featureTypes) {
addFeatures: function (features, filter, addToMap = true, featureTypes, project_slug) {
const featureGroup = new L.FeatureGroup();
features.forEach((feature) => {
const featureProperties = feature.properties ? feature.properties : feature;
......@@ -391,7 +389,7 @@ const mapUtil = {
filters.length && filters.every(val => val !== false)
) {
const geomJSON = flip(feature.geometry || feature.geom);
const popupContent = this._createContentPopup(feature);
const popupContent = this._createContentPopup(feature, featureTypes, project_slug);
// Look for a custom field
let customField;
......@@ -404,14 +402,16 @@ const mapUtil = {
.filter(el => featureType.customfield_set.map(e => e.name).includes(el));
customFieldOption = featureProperties[customField[0]];
}
let color = this.retrieveFeatureColor(featureType, featureProperties) || featureProperties.color;
if (color == undefined){
color = featureType.color;
let color = '#000000';
if (feature.overideColor) {
color = feature.overideColor;
} else {
color = this.retrieveFeatureColor(featureType, featureProperties) || featureProperties.color;
if (color.value && color.value.length) {
color = color.value;
}
}
const colorValue =
color.value && color.value.length ?
color.value : typeof color === 'string' && color.length ?
color : '#000000';
if (geomJSON.type === 'Point') {
if (
customFieldOption &&
......@@ -427,7 +427,7 @@ const mapUtil = {
const iconHTML = `
<i
class="fas fa-${featureType.colors_style.value.icons[customFieldOption]} fa-lg"
style="color: ${colorValue}"
style="color: ${color}"
></i>
`;
const customMapIcon = L.divIcon({
......@@ -437,14 +437,14 @@ const mapUtil = {
});
L.marker(geomJSON.coordinates, {
icon: customMapIcon,
color: colorValue,
color: color,
zIndexOffset: 100
})
.bindPopup(popupContent)
.addTo(featureGroup);
} else {
L.circleMarker(geomJSON.coordinates, {
color: colorValue,
color: color,
radius: 4,
fillOpacity: 0.5,
weight: 3,
......@@ -457,7 +457,7 @@ const mapUtil = {
const iconHTML = `
<i
class="fas fa-${featureType.icon} fa-lg"
style="color: ${colorValue}"
style="color: ${color}"
></i>
`;
const customMapIcon = L.divIcon({
......@@ -467,7 +467,7 @@ const mapUtil = {
});
L.marker(geomJSON.coordinates, {
icon: customMapIcon,
color: colorValue,
color: color,
zIndexOffset: 100
})
.bindPopup(popupContent)
......@@ -485,14 +485,14 @@ const mapUtil = {
}
} else if (geomJSON.type === 'LineString') {
L.polyline(geomJSON.coordinates, {
color: colorValue,
color: color,
weight: 3,
})
.bindPopup(popupContent)
.addTo(featureGroup);
} else if (geomJSON.type === 'Polygon') {
L.polygon(geomJSON.coordinates, {
color: colorValue,
color: color,
weight: 3,
fillOpacity: 0.5,
})
......@@ -501,6 +501,7 @@ const mapUtil = {
}
}
});
if (map && addToMap) {
map.addLayer(featureGroup);
}
......@@ -517,32 +518,26 @@ const mapUtil = {
('0' + current_datetime.getHours()).slice(-2) + ':' + ('0' + current_datetime.getMinutes()).slice(-2);
return formatted_date;
};
let feature_type;
let status;
let date_maj;
let feature_type_url;
let feature_url;
let feature_type = feature.properties ? feature.properties.feature_type : feature.feature_type;
let feature_url = feature.feature_url;
let status = feature.status;
let feature_type_url = feature.feature_type_url;
let date_maj = feature.updated_on;
if (feature.properties) {
status = feature.properties.status;
date_maj = feature.properties.updated_on;
date_maj = feature.properties.updated_on ? formatDate(new Date(feature.properties.updated_on)) : '<i>indisponible</i>';
feature_type_url = feature.properties.feature_type_url;
feature_url = feature.properties.feature_url;
} else {
status = feature.status;
date_maj = feature.updated_on;
feature_type_url =feature.feature_type_url;
feature_url = feature.feature_url;
}
if (featureTypes) { // => VectorTile
feature_type = featureTypes.find((x) => x.slug.split('-')[0] === '' + feature.properties.feature_type_id);
if (featureTypes && feature.properties) { // => VectorTile
feature_type = feature.properties.feature_type_id ?
featureTypes.find((x) => x.slug.split('-')[0] === '' + feature.properties.feature_type_id) :
featureTypes.find((f_type) => f_type.slug === feature.properties.feature_type); //* geojson
status = statusList.find((x) => x.value === feature.properties.status).name;
date_maj = formatDate(new Date(feature.properties.updated_on));
feature_type_url = '/geocontrib/projet/' + project_slug + '/type-signalement/' + feature_type.slug + '/';
if (feature_type) feature_type_url = '/geocontrib/projet/' + project_slug + '/type-signalement/' + feature_type.slug + '/';
feature_url = feature_type_url + 'signalement/' + feature.properties.feature_id + '/';
} else {
feature_type = feature.properties ? feature.properties.feature_type : feature.feature_type;
status = feature.properties ? feature.properties.status.label : feature.status.label;
}
......@@ -556,27 +551,37 @@ const mapUtil = {
if (creator) {
author = creator.full_name
? `<div>
Auteur : ${creator.first_name} ${creator.last_name}
</div>`
Auteur : ${creator.first_name} ${creator.last_name}
</div>`
: creator.username ? `<div>Auteur: ${creator.username}</div>` : '';
}
const title = feature.properties ? feature.properties.title : feature.title;
let title = feature.properties ? feature.properties.title : feature.title;
if (feature_url) {
title = `<a href="${feature_url}">${title}</a>`;
} else {
title = `<span>${title|| '<i>indisponible</i>'}</span>`;
}
if (feature_type_url) {
`<a href="${feature_type_url}"> ${feature_type.title || '<i>indisponible</i>'} </a>`;
}
return `
<h4>
<a href="${feature_url}">${title}</a>
</h4>
<div>
Statut : ${status}
</div>
<div>
Type : <a href="${feature_type_url}"> ${feature_type.title} </a>
</div>
<div>
Dernière mise à jour : ${date_maj}
</div>
${author}
`;
<h4>
<${feature_url ? 'a href="' + feature_url + '"' : 'span'}>${title || '<i>indisponible</i>'}</${feature_url ? 'a' : 'span'}>
</h4>
<div>
Statut : ${status || '<i>indisponible</i>'}
</div>
<div>
Type : <${feature_type_url ? 'a href="'+ feature_type_url + '"' : 'span'}> ${feature_type.title || '<i>indisponible</i>'} </${feature_type_url ? 'a' : 'span'}>
</div>
<div>
Dernière mise à jour : ${date_maj || '<i>indisponible</i>'}
</div>
${author}
`;
},
};
......
......@@ -7,7 +7,28 @@ export function fileConvertSize(aSize){
}
export function fileConvertSizeToMo(aSize){
console.log(aSize);
aSize = Math.abs(parseInt(aSize, 10));
const def = [1024*1024, 'Mo', 1];
return (aSize/def[0]).toFixed(def[2]);
}
export function csvToJson(csv) {
let result = [];
const allLines = csv.split('\n');
const headers = allLines[0].split(',');
const [, ...lines] = allLines;
for (const line of lines) {
let obj = {};
const currentLine = line.split(',');
for (let i = 0; i < headers.length; i++) {
obj[headers[i]] = currentLine[i];
}
result.push(obj);
}
return JSON.parse(JSON.stringify(result));
}
......@@ -125,11 +125,10 @@ export default {
methods: {
toggleDropdown(val) {
if (this.isOpen) {
this.input = ''; // * clear input field when closing dropdown
} else if (this.search) {
//* focus on input if is a search dropdown
this.$refs.input.focus({
if (this.isOpen) { //* if dropdown is open :
this.input = ''; // * -> clear input field when closing dropdown
} else if (this.search) { //* if dropdown is closed is a search dropdown:
this.$refs.input.focus({ //* -> focus on input field
preventScroll: true,
});
} else if (this.clearable && val.target && this.selected) {
......@@ -172,8 +171,9 @@ export default {
},
clickOutsideDropdown(e) {
if (!e.target.closest(`#custom-dropdown${this.identifier}`))
if (!e.target.closest(`#custom-dropdown${this.identifier}`) && this.isOpen) {
this.toggleDropdown(false);
}
},
},
};
......
......@@ -12,7 +12,7 @@
selected-label=""
deselect-label=""
:searchable="true"
:placeholder="'Rechercher un signalement ...'"
:placeholder="placeholder"
:show-no-results="true"
:loading="loading"
:clear-on-select="false"
......@@ -52,12 +52,20 @@ export default {
Multiselect
},
props: {
currentSelection : {
type: Object,
default: null,
}
},
data() {
return {
loading: false,
selection: null,
text: null,
results: []
results: [],
placeholder: 'Rechercher un signalement ...'
};
},
......@@ -91,6 +99,12 @@ export default {
this.RESET_CANCELLABLE_SEARCH_REQUEST();
},
mounted() {
if (this.currentSelection && this.currentSelection.feature_to) {
this.placeholder = this.currentSelection.feature_to.title;
}
},
methods: {
...mapMutations(['RESET_CANCELLABLE_SEARCH_REQUEST']),
...mapActions('feature', [
......@@ -109,6 +123,19 @@ export default {
};
</script>
<style scoped>
<style>
.multiselect input {
line-height: 1em !important;
padding: 0 !important;
}
.multiselect__placeholder {
margin: 0;
padding: 0;
}
.multiselect__input {
min-height: auto !important;
line-height: 1em !important;
}
</style>
......@@ -24,7 +24,7 @@
<div class="visible-fields">
<div class="two fields">
<div class="required field">
<label for="form.relation_type.id_for_label">{{
<label :for="form.relation_type.id_for_label">{{
form.relation_type.label
}}</label>
<Dropdown
......@@ -35,10 +35,11 @@
{{ form.relation_type.errors }}
</div>
<div class="required field">
<label for="form.feature_to.id_for_label">{{
<label :for="form.feature_to.id_for_label">{{
form.feature_to.label
}}</label>
<SearchFeature
:current-selection="linkedForm"
@select="selectFeatureTo"
@close="selectFeatureTo"
/>
......@@ -90,10 +91,7 @@ export default {
},
html_name: 'feature_to',
label: 'Signalement lié',
value: {
name: '',
value: '',
},
value: '',
},
},
relationTypeChoices: [
......@@ -106,7 +104,6 @@ export default {
},
computed: {
selected_relation_type: {
// getter
get() {
......@@ -122,23 +119,24 @@ export default {
mounted() {
if (this.linkedForm.relation_type) {
this.getExistingRelation_type();
this.form.relation_type.value.name = this.linkedForm.relation_type_display;
this.form.relation_type.value.value = this.linkedForm.relation_type;
}
if (this.linkedForm.feature_to) {
this.form.feature_to.value = this.linkedForm.feature_to.feature_id;
}
},
methods: {
formatDate(value) {
let date = new Date(value);
date = date.toLocaleString().replace(',', '');
return date.substr(0, date.length - 3); //* quick & dirty way to remove seconds from date
},
remove_linked_formset() {
this.$store.commit('feature/REMOVE_LINKED_FORM', this.linkedForm.dataKey);
},
selectFeatureTo(e) {
this.form.feature_to.value = e;
selectFeatureTo(feature) {
if (feature && feature.feature_id) {
this.form.feature_to.value = feature.feature_id;
this.updateStore();
}
},
updateStore() {
......@@ -146,7 +144,7 @@ export default {
dataKey: this.linkedForm.dataKey,
relation_type: this.form.relation_type.value.value,
feature_to: {
feature_id: this.form.feature_to.value.value,
feature_id: this.form.feature_to.value,
},
});
},
......@@ -154,7 +152,7 @@ export default {
checkForm() {
if (this.form.feature_to.value === '') {
this.form.errors = [
'<strong>Choisir un signalement lié</strong><br/> Pourriez-vous choisir un signalement pour la nouvelle liaison ?',
'Veuillez choisir un signalement pour la nouvelle liaison.',
];
document
.getElementById('errorlist-links')
......@@ -164,24 +162,6 @@ export default {
this.form.errors = [];
return true;
},
getExistingFeature_to(featureOptions) {
const feature_to = featureOptions.find(
(el) => el.value === this.linkedForm.feature_to.feature_id
);
if (feature_to) {
this.form.feature_to.value = feature_to;
}
},
getExistingRelation_type() {
const relation_type = this.relationTypeChoices.find(
(el) => el.value === this.linkedForm.relation_type
);
if (relation_type) {
this.form.relation_type.value = relation_type;
}
},
},
};
</script>
\ No newline at end of file
......@@ -125,10 +125,10 @@
:class="['ui checkbox', {disabled: !checkRights(feature)}]"
>
<input
:id="feature.id"
:id="feature.feature_id"
v-model="checked"
type="checkbox"
:value="feature.id"
:value="feature.feature_id"
:disabled="!checkRights(feature)"
name="select"
@input="storeClickedFeature(feature)"
......@@ -138,25 +138,29 @@
</td>
<td class="dt-center">
<div v-if="feature.properties.status.value === 'archived'">
<span data-tooltip="Archivé">
<i class="grey archive icon" />
</span>
<div
v-if="feature.status === 'archived'"
data-tooltip="Archivé"
>
<i class="grey archive icon" />
</div>
<div v-else-if="feature.properties.status.value === 'pending'">
<span data-tooltip="En attente de publication">
<i class="teal hourglass outline icon" />
</span>
<div
v-else-if="feature.status === 'pending'"
data-tooltip="En attente de publication"
>
<i class="teal hourglass outline icon" />
</div>
<div v-else-if="feature.properties.status.value === 'published'">
<span data-tooltip="Publié">
<i class="olive check icon" />
</span>
<div
v-else-if="feature.status === 'published'"
data-tooltip="Publié"
>
<i class="olive check icon" />
</div>
<div v-else-if="feature.properties.status.value === 'draft'">
<span data-tooltip="Brouillon">
<i class="orange pencil alternate icon" />
</span>
<div
v-else-if="feature.status === 'draft'"
data-tooltip="Brouillon"
>
<i class="orange pencil alternate icon" />
</div>
</td>
<td class="dt-center">
......@@ -164,11 +168,11 @@
:to="{
name: 'details-type-signalement',
params: {
feature_type_slug: feature.properties.feature_type.slug,
feature_type_slug: feature.feature_type.slug,
},
}"
>
{{ feature.properties.feature_type.title }}
{{ feature.feature_type.title }}
</router-link>
</td>
<td class="dt-center">
......@@ -176,28 +180,28 @@
:to="{
name: 'details-signalement',
params: {
slug_type_signal: feature.properties.feature_type.slug,
slug_signal: feature.properties.slug || feature.id,
slug_type_signal: feature.feature_type.slug,
slug_signal: feature.slug || feature.feature_id,
},
}"
>
{{ getFeatureDisplayName(feature) }}
{{ feature.title || feature.feature_id }}
</router-link>
</td>
<td class="dt-center">
{{ feature.properties.updated_on }}
{{ feature.updated_on | formatDate }}
</td>
<td
v-if="user"
class="dt-center"
>
{{ getUserName(feature) }}
{{ feature.display_creator || ' ---- ' }}
</td>
<td
v-if="user"
class="dt-center"
>
{{ feature.properties.display_last_editor }}
{{ feature.display_last_editor || ' ---- ' }}
</td>
</tr>
<tr
......@@ -296,11 +300,17 @@
<script>
import { mapState, mapGetters, mapMutations } from 'vuex';
import FeatureListMassToggle from '@/components/feature/FeatureListMassToggle';
import { formatStringDate } from '@/utils';
export default {
name: 'FeatureListTable',
filters: {
formatDate(value) {
return formatStringDate(value);
},
},
components: {
FeatureListMassToggle,
},
......@@ -400,7 +410,7 @@ export default {
canDeleteFeature(feature) {
if (this.userStatus === 'Administrateur projet') return true; //* can delete all
//* others can delete only their own features
return feature.properties.creator.username === this.user.username;
return feature.display_creator === this.user.username;
},
canEditFeature(feature) {
......@@ -411,10 +421,10 @@ export default {
Contributeur : ['draft', 'pending', 'published'],
};
if (this.userStatus === 'Contributeur' && feature.properties.creator.username !== this.user.username) {
if (this.userStatus === 'Contributeur' && feature.display_creator !== this.user.username) {
return false;
} else if (permissions[this.userStatus]) {
return permissions[this.userStatus].includes(feature.properties.status.value);
return permissions[this.userStatus].includes(feature.status);
} else {
return false;
}
......@@ -429,14 +439,10 @@ export default {
}
},
getUserName(feature) {
if (!feature.properties.creator) {
return ' ---- ';
}
return feature.properties.creator.username || ' ---- ';
},
getFeatureDisplayName(feature) {
return feature.properties.title || feature.id;
switchMode() {
this.$emit('update:mode', this.mode === 'modify' ? 'delete' : 'modify');
this.UPDATE_CLICKED_FEATURES([]);
this.$store.commit('feature/UPDATE_CHECKED_FEATURES', []);
},
isSortedAsc(column) {
......
......@@ -52,9 +52,9 @@ if (workbox) {
})
);
workbox.routing.registerRoute(
/^https:\/\/osm\.geo2france\.fr\/mapcache/,
new RegExp('.*/service=WMS&request=GetMap/.*'),
new workbox.strategies.CacheFirst({
cacheName: 'mapcache',
cacheName: 'wms',
plugins: [
new workbox.cacheableResponse.Plugin({
statuses: [0, 200],
......
......@@ -13,8 +13,8 @@ const feature = {
features_count: 0,
current_feature: [],
form: null,
linkedFormset: [],
linked_features: [],
linkedFormset: [], //* used to edit in feature_edit
linked_features: [], //* used to display in feature_detail
massMode: 'modify',
statusChoices: [
{
......@@ -168,7 +168,7 @@ const feature = {
const cancelToken = axios.CancelToken.source();
commit('SET_CANCELLABLE_SEARCH_REQUEST', cancelToken, { root: true });
commit('SET_CURRENT_FEATURE', null);
//commit('SET_CURRENT_FEATURE', null); //? Est-ce que c'est nécessaire ? -> fait sauter l'affichage au clic sur un signalement lié (feature_detail)
let url = `${rootState.configuration.VUE_APP_DJANGO_API_BASE}projects/${project_slug}/feature/?id=${feature_id}`;
return axios
.get(url, { cancelToken: cancelToken.token })
......
......@@ -210,6 +210,35 @@ const feature_type = {
});
},
async SEND_FEATURES_FROM_CSV({ state, dispatch }, payload) {
let { feature_type_slug, csv } = payload;
if(!csv && !state.fileToImport && state.fileToImport.size === 0 ) return;
let formData = new FormData();
formData.append('csv_file', state.fileToImport);
formData.append('feature_type_slug', feature_type_slug);
const url = `${this.state.configuration.VUE_APP_DJANGO_API_BASE}import-tasks/`;
return axios
.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
.then((response) => {
if (response && response.status === 200) {
return dispatch('GET_IMPORTS', {
feature_type: feature_type_slug
});
}
return response;
})
.catch((error) => {
throw (error);
});
},
GET_IMPORTS({ commit }, { project_slug, feature_type }) {
let url = `${this.state.configuration.VUE_APP_DJANGO_API_BASE}import-tasks/`;
if (project_slug) {
......
export function parseDate(date) {
let dateArr = date.split('/').reverse();
return new Date(dateArr[0], dateArr[1] - 1, dateArr[2]);
export function formatStringDate(stringDate) {
const date = new Date(stringDate);
const formatted_date = date.getFullYear() + '/' + ('0' + (date.getMonth() + 1)).slice(-2) + '/' + ('0' + date.getDate()).slice(-2) + ' ' +
('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2);
return formatted_date;
}
export function allowedStatus2change(statusChoices, isModerate, userStatus, isOwnFeature, currentRouteName) {
......
This diff is collapsed.
......@@ -647,8 +647,7 @@ export default {
for (const linked of linkedFormset) {
this.$store.commit('feature/ADD_LINKED_FORM', {
dataKey: this.linkedDataKey,
relation_type: linked.relation_type,
feature_to: linked.feature_to,
...linked
});
this.linkedDataKey += 1;
}
......@@ -984,6 +983,10 @@ export default {
const allFeaturesExceptCurrent = features.filter(
(feat) => feat.id !== currentFeatureId
);
console.log(allFeaturesExceptCurrent);
console.log(allFeaturesExceptCurrent[0].geometry);
console.log(allFeaturesExceptCurrent[0].geometry.coordinates);
console.log(allFeaturesExceptCurrent[0].geometry.coordinates[0]);
mapUtil.addFeatures(
allFeaturesExceptCurrent,
{},
......
......@@ -590,7 +590,7 @@ export default {
},
fetchPagedFeatures(newUrl) {
let url = `${this.API_BASE_URL}projects/${this.projectSlug}/feature-paginated/?output=geojson&limit=${this.pagination.pagesize}&offset=${this.pagination.start}`;
let url = `${this.API_BASE_URL}projects/${this.projectSlug}/feature-paginated/?limit=${this.pagination.pagesize}&offset=${this.pagination.start}`;
//* if receiving next & previous url (// todo : might be not used anymore, to check)
if (newUrl && typeof newUrl === 'string') {
//newUrl = newUrl.replace("8000", "8010"); //* for dev uncomment to use proxy link
......@@ -608,7 +608,7 @@ export default {
this.featuresCount = data.count;
this.previous = data.previous;
this.next = data.next;
this.paginatedFeatures = data.results.features;
this.paginatedFeatures = data.results;
}
//* bbox needs to be updated with the same filters
if (this.paginatedFeatures.length) {
......
......@@ -89,7 +89,7 @@
for="json_file"
>
<i class="file icon" />
<span class="label">{{ fileToImport.name }}</span>
<span class="label">{{ geojsonFileToImport.name }}</span>
</label>
<input
id="json_file"
......@@ -97,7 +97,25 @@
accept="application/json, .json, .geojson"
style="display: none"
name="json_file"
@change="onFileChange"
@change="onGeojsonFileChange"
>
</div>
<div class="field">
<label
class="ui icon button ellipsis"
for="csv_file"
>
<i class="file icon" />
<span class="label">{{ csvFileToImport.name }}</span>
</label>
<input
id="csv_file"
type="file"
accept="application/csv, .csv"
style="display: none"
name="csv_file"
@change="onCsvFileChange"
>
</div>
......@@ -133,9 +151,12 @@
</li>
</ul>
<button
:disabled="fileToImport.size === 0 && !$route.params.geojson"
:disabled="
(geojsonFileToImport.size === 0 && !$route.params.geojson) &&
(csvFileToImport.size === 0 && !$route.params.csv)
"
class="ui fluid teal icon button"
@click="importGeoJson"
@click="geojsonFileToImport.size !== 0 ? importGeoJson() : importCSV()"
>
<i class="upload icon" /> Lancer l'import
</button>
......@@ -160,6 +181,30 @@
Vous pouvez télécharger tous les signalements qui vous sont
accessibles.
</p>
<!-- <div class="ui selection dropdown fluid">
<input type="hidden" name="format">
<i class="dropdown icon"></i>
<div class="default text">Format</div>
<div class="menu">
<div class="item" data-value="1">GeoJSON</div>
<div class="item" data-value="2">CSV</div>
</div>
</div> -->
<select
v-model="exportFormat"
class="ui fluid dropdown"
style="margin-bottom: 1em;"
>
<option value="GeoJSON">
GeoJSON
</option>
<option value="CSV">
CSV
</option>
</select>
<button
type="button"
class="ui fluid teal icon button"
......@@ -287,10 +332,11 @@
<script>
import { mapActions, mapMutations, mapGetters, mapState } from 'vuex';
import { formatStringDate } from '@/utils';
import ImportTask from '@/components/ImportTask';
import featureAPI from '@/services/feature-api';
import { fileConvertSizeToMo } from '@/assets/js/utils';
import { fileConvertSizeToMo, csvToJson } from '@/assets/js/utils';
export default {
name: 'FeatureTypeDetail',
......@@ -301,9 +347,7 @@ export default {
filters: {
formatDate(value) {
let date = new Date(value);
date = date.toLocaleString().replace(',', ' à');
return date.substr(0, date.length - 3); //* quick & dirty way to remove seconds from date
return formatStringDate(value);
},
},
......@@ -312,21 +356,30 @@ export default {
type: Object,
default: null,
},
csv: {
type: Object,
default: null,
},
},
data() {
return {
importError: '',
fileToImport: {
geojsonFileToImport: {
name: 'Sélectionner un fichier GeoJSON ...',
size: 0,
},
csvFileToImport: {
name: 'Sélectionner un fichier CSV ...',
size: 0,
},
showImport: false,
slug: this.$route.params.slug,
featuresLoading: true,
loadingImportFile: false,
waitMessage: false,
reloadingImport: false,
exportFormat: 'GeoJSON'
};
},
......@@ -504,7 +557,64 @@ export default {
return true;
},
onFileChange(e) {
checkCsvValidity(csv) {
this.importError = '';
// Check if file contains 'lat' and 'long' fields
const headersLine =
csv
.split('\n')[0]
.split(',')
.filter(el => {
return el === 'lat' || el === 'lon';
});
// Look for 2 decimal fields in first line of csv
// corresponding to lon and lat
const sampleLine =
csv
.split('\n')[1]
.split(',')
.map(el => {
return !isNaN(el) && el.indexOf('.') != -1;
})
.filter(Boolean);
if (sampleLine.length > 1 && headersLine.length === 2) {
const fields = this.structure.customfield_set.map((el) => {
return {
name: el.name,
field_type: el.field_type,
options: el.options,
};
});
const csvFeatures = csvToJson(csv);
for (const feature of csvFeatures) {
for (const { name, field_type, options } of fields) {
if (name in feature) {
const fieldInFeature = feature[name];
const customType = this.transformProperties(fieldInFeature);
//* if custom field value is not null, then check validity of field
if (fieldInFeature !== null) {
//* if field type is list, it's not possible to guess from value type
if (field_type === 'list') {
//*then check if the value is an available option
if (!options.includes(fieldInFeature)) {
return false;
}
} else if (customType !== field_type) {
//* check if custom field value match
this.importError = `Le fichier est invalide: Un champ de type ${field_type} ne peut pas avoir la valeur [ ${fieldInFeature} ]`;
return false;
}
}
}
}
}
return true;
} else {
return false;
}
},
onGeojsonFileChange(e) {
this.loadingImportFile = true;
const files = e.target.files || e.dataTransfer.files;
if (!files.length) {
......@@ -523,10 +633,42 @@ export default {
}
if (jsonValidity) {
this.fileToImport = files[0]; // todo : remove this value from state as it stored (first attempt didn't work)
this.geojsonFileToImport = files[0]; // todo : remove this value from state as it stored (first attempt didn't work)
this.$store.commit(
'feature_type/SET_FILE_TO_IMPORT',
this.fileToImport
this.geojsonFileToImport
);
}
this.loadingImportFile = false;
});
reader.readAsText(files[0]);
},
onCsvFileChange(e) {
this.loadingImportFile = true;
const files = e.target.files || e.dataTransfer.files;
console.log(files);
if (!files.length) {
this.loadingImportFile = false;
return;
}
let reader = new FileReader();
reader.addEventListener('load', (e) => {
console.log(e);
// bypass csv check for files larger then 10 Mo
let csvValidity;
if (parseFloat(fileConvertSizeToMo(files[0].size)) <= 10) {
csvValidity = this.checkCsvValidity(e.target.result);
} else {
csvValidity = true;
}
if (csvValidity) {
this.csvFileToImport = files[0]; // todo : remove this value from state as it stored (first attempt didn't work)
this.$store.commit(
'feature_type/SET_FILE_TO_IMPORT',
this.csvFileToImport
);
}
this.loadingImportFile = false;
......@@ -542,8 +684,8 @@ export default {
};
if (this.$route.params.geojson) { //* import after redirection, for instance with data from catalog
payload['geojson'] = this.$route.params.geojson;
} else if (this.fileToImport.size > 0) { //* import directly from geojson
payload['fileToImport'] = this.fileToImport;
} else if (this.geojsonFileToImport.size > 0) { //* import directly from geojson
payload['fileToImport'] = this.geojsonFileToImport;
} else {
this.importError = "La ressource n'a pas pu être récupéré.";
return;
......@@ -554,6 +696,26 @@ export default {
});
},
importCSV() {
this.waitMessage = true;
let payload = {
slug: this.slug,
feature_type_slug: this.$route.params.feature_type_slug,
};
if (this.$route.params.csv) { //* import after redirection, for instance with data from catalog
payload['csv'] = this.$route.params.csv;
} else if (this.csvFileToImport.size > 0) { //* import directly from geojson
payload['fileToImport'] = this.csvFileToImport;
} else {
this.importError = "La ressource n'a pas pu être récupéré.";
return;
}
this.$store.dispatch('feature_type/SEND_FEATURES_FROM_CSV', payload)
.then(() => {
this.waitMessage = false;
});
},
exportFeatures() {
const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.slug}/feature-type/${this.$route.params.feature_type_slug}/export/`;
featureAPI.getFeaturesBlob(url).then((blob) => {
......
......@@ -64,7 +64,10 @@
</ul>
</div>
<div class="required field">
<div
:class="{ disabled: csv }"
class="required field"
>
<label :for="form.geom_type.id_for_label">{{
form.geom_type.label
}}</label>
......@@ -147,46 +150,113 @@
<span v-if="action === 'duplicate' || action === 'edit'" />
<div id="formsets">
<FeatureTypeCustomForm
v-for="customForm in customForms"
:key="customForm.dataKey"
ref="customForms"
:data-key="customForm.dataKey"
:custom-form="customForm"
:selected-color-style="form.colors_style.value.custom_field_name"
@update="updateColorsStyle($event)"
/>
</div>
<button
id="add-field"
type="button"
class="ui compact basic button"
@click="addCustomForm"
>
<i class="ui plus icon" />Ajouter un champ personnalisé
</button>
<div class="ui divider" />
<button
class="ui teal icon button margin-25"
type="button"
@click="sendFeatureType"
>
<i class="white save icon" />
{{ action === "create" ? "Créer" : "Sauvegarder" }} le type de
signalement
</button>
<button
v-if="geojson"
class="ui teal icon button margin-25"
type="button"
@click="postFeatureTypeThenFeatures"
<!-- <div
v-if="csvFields && csvFields.length"
id="csv-fields"
>
<i class="white save icon" />
Créer et importer le(s) signalement(s) du geojson
</button>
<table class="ui striped table">
<thead>
<tr>
<th>Champ</th>
<th>X</th>
<th>Y</th>
</tr>
</thead>
<tbody>
<tr
v-for="field in csvFields"
:key="field.field"
>
<td>
{{ field.field }}
</td>
<td>
<div
class="ui radio checkbox"
:class="{ disabled: field.y }"
>
<input
:disabled="field.y"
type="radio"
name="x"
@input="pickXcsvCoordField(field)"
>
<label />
</div>
</td>
<td>
<div
class="ui radio checkbox"
:class="{ disabled: field.x }"
>
<input
:disabled="field.x"
type="radio"
name="y"
@input="pickYcsvCoordField(field)"
>
<label />
</div>
</td>
</tr>
</tbody>
</table>
<button
class="ui teal icon button margin-25"
type="button"
:disabled="
!csvFields.some(el => el.x === true) ||
!csvFields.some(el => el.y === true)
"
@click="setCSVCoordsFields"
>
<i class="white save icon" />
Continuer
</button>
</div> -->
<div v-else>
<div id="formsets">
<FeatureTypeCustomForm
v-for="customForm in customForms"
:key="customForm.dataKey"
ref="customForms"
:data-key="customForm.dataKey"
:custom-form="customForm"
:selected-color-style="form.colors_style.value.custom_field_name"
@update="updateColorsStyle($event)"
/>
</div>
<button
id="add-field"
type="button"
class="ui compact basic button"
@click="addCustomForm"
>
<i class="ui plus icon" />Ajouter un champ personnalisé
</button>
<div class="ui divider" />
<button
class="ui teal icon button margin-25"
type="button"
@click="sendFeatureType"
>
<i class="white save icon" />
{{ action === "create" ? "Créer" : "Sauvegarder" }} le type de
signalement
</button>
<button
v-if="geojson || csv"
class="ui teal icon button margin-25"
type="button"
@click="postFeatureTypeThenFeatures"
>
<i class="white save icon" />
Créer et importer le(s) signalement(s) du geojson
</button>
</div>
</form>
</div>
</div>
......@@ -215,6 +285,10 @@ export default {
type: Object,
default: null,
},
csv: {
type: Array,
default: null,
},
},
data() {
......@@ -223,6 +297,7 @@ export default {
action: 'create',
dataKey: 0,
error: null,
csvFields: null,
geomTypeChoices: [
{ value: 'linestring', name: 'Ligne' },
{ value: 'point', name: 'Point' },
......@@ -287,6 +362,8 @@ export default {
'display_last_editor',
'project',
'creator',
'lat',
'lon'
],
};
},
......@@ -399,11 +476,20 @@ export default {
this.importGeoJsonFeatureType();
if (this.fileToImport && this.fileToImport.name) {
this.form.title.value = // * use the filename as title by default
this.fileToImport.name.split('.')[0];
this.fileToImport.name.split('.')[0];
} else { //* case when the geojson comes from datasud catalog
this.form.title.value = this.geojson.name;// * use the typename as title by default
}
}
if (this.csv) {
this.importCSVFeatureType();
if (this.fileToImport && this.fileToImport.name) {
this.form.title.value = // * use the filename as title by default
this.fileToImport.name.split('.')[0];
} else { //* case when the geojson comes from datasud catalog
this.form.title.value = this.csv.name;// * use the typename as title by default
}
}
},
beforeDestroy() {
this.$store.commit('feature_type/EMPTY_FORM');
......@@ -553,7 +639,7 @@ export default {
}
},
postFeatures(feature_type_slug) {
postGeojsonFeatures(feature_type_slug) {
this.$store
.dispatch('feature_type/SEND_FEATURES_FROM_GEOJSON', {
slug: this.slug,
......@@ -575,6 +661,28 @@ export default {
this.loading = false;
});
},
postCSVFeatures(feature_type_slug) {
this.$store
.dispatch('feature_type/SEND_FEATURES_FROM_CSV', {
slug: this.slug,
feature_type_slug,
csv: this.csv
})
.then((response) => {
if (response && response.status === 200) {
this.goBackToProject();
} else {
this.displayMessage(
"Une erreur est survenue lors de l'import de signalements.\n " +
response.data.detail
);
}
this.loading = false;
})
.catch(() => {
this.loading = false;
});
},
async postFeatureTypeThenFeatures() {
const requestType = this.action === 'edit' ? 'put' : 'post';
......@@ -584,7 +692,12 @@ export default {
.dispatch('feature_type/SEND_FEATURE_TYPE', requestType)
.then(({ feature_type_slug }) => {
if (feature_type_slug) {
this.postFeatures(feature_type_slug);
if (this.geojson) {
this.postGeojsonFeatures(feature_type_slug);
}
else if (this.csv) {
this.postCSVFeatures(feature_type_slug);
}
} else {
this.loading = false;
}
......@@ -663,6 +776,76 @@ export default {
}
}
},
importCSVFeatureType() {
if (this.csv.length) {
this.updateStore(); //* register title & geom_type in store
// List fileds for user to select coords fields
// this.csvFields =
// Object.keys(this.csv[0])
// .map(el => {
// return {
// field: el,
// x: false,
// y:false
// };
// });
for (const [key, val] of Object.entries(this.csv[0])) {
//* check that the property is not a keyword from the backend or map style
// todo: add map style keywords
if (!this.reservedKeywords.includes(key)) {
const customForm = {
label: { value: key || '' },
name: { value: key || '' },
position: this.dataKey, // * use dataKey already incremented by addCustomForm
field_type: { value: this.transformProperties(val) }, // * guessed from the type
options: { value: [] }, // * not available in export
};
this.addCustomForm(customForm);
}
}
}
},
// pickXcsvCoordField(e) {
// this.csvFields.forEach(el => {
// if (el.field === e.field) {
// el.x = true;
// } else {
// el.x = false;
// }
// });
// },
// pickYcsvCoordField(e) {
// this.csvFields.forEach(el => {
// if (el.field === e.field) {
// el.y = true;
// } else {
// el.y = false;
// }
// });
// },
// setCSVCoordsFields() {
// const xField = this.csvFields.find(el => el.x === true).field;
// const yField = this.csvFields.find(el => el.y === true).field;
// this.csvFields = null;
// for (const [key, val] of Object.entries(this.csv[0])) {
// //* check that the property is not a keyword from the backend or map style
// // todo: add map style keywords
// if (!this.reservedKeywords.includes(key) && key !== xField && key !== yField) {
// const customForm = {
// label: { value: key || '' },
// name: { value: key || '' },
// position: this.dataKey, // * use dataKey already incremented by addCustomForm
// field_type: { value: this.transformProperties(val) }, // * guessed from the type
// options: { value: [] }, // * not available in export
// };
// this.addCustomForm(customForm);
// }
// }
// }
},
};
</script>
......
......@@ -372,6 +372,7 @@
<i class="ui plus icon" />Créer un nouveau type de signalement
</router-link>
</div>
<div class="nouveau-type-signalement">
<div
v-if="
......@@ -403,7 +404,43 @@
accept="application/json, .json, .geojson"
style="display: none"
name="json_file"
@change="onFileChange"
@change="onGeoJSONFileChange"
>
</div>
</div>
<div class="nouveau-type-signalement">
<div
v-if="
permissions &&
permissions.can_update_project &&
isOnline
"
class="
ui
compact
basic
button
button-align-left
"
>
<i class="ui plus icon" />
<label
class="ui"
for="csv_file"
>
<span
class="label"
>Créer un nouveau type de signalement à partir d'un
CSV</span>
</label>
<input
id="csv_file"
type="file"
accept="application/csv, .csv"
style="display: none"
name="csv_file"
@change="onCSVFileChange"
>
</div>
</div>
......@@ -431,19 +468,44 @@
</div>
<div
v-if="fileToImport.size > 0"
v-if="geojsonFileToImport.size > 0"
id="button-import"
>
<button
:disabled="geojsonFileToImport.size === 0"
class="ui fluid teal icon button"
@click="toNewGeojsonFeatureType"
>
<i class="upload icon" /> Lancer l'import avec le fichier
{{ geojsonFileToImport.name }}
</button>
</div>
<div
v-if="csvFileToImport.size > 0 && !csvError"
id="button-import"
>
<button
:disabled="fileToImport.size === 0"
:disabled="csvFileToImport.size === 0"
class="ui fluid teal icon button"
@click="toNewFeatureType"
@click="toNewCsvFeatureType"
>
<i class="upload icon" /> Lancer l'import avec le fichier
{{ fileToImport.name }}
{{ csvFileToImport.name }}
</button>
</div>
<div
v-if="csvError"
class="ui negative message"
>
<i
class="close icon"
@click="csvError = null"
/>
{{ csvError }}
</div>
</div>
<div
id="map-column"
class="seven wide column"
......@@ -590,8 +652,8 @@
<h3 class="ui header">
Paramètres du projet
</h3>
<div class="ui five stackable cards">
<div class="card">
<div class="ui three stackable cards">
<!-- <div class="card">
<div class="center aligned content">
<h4 class="ui center aligned icon header">
<i class="disabled grey archive icon" />
......@@ -616,7 +678,7 @@
<div class="center aligned extra content">
{{ project.delete_feature }} jours
</div>
</div>
</div> -->
<div class="card">
<div class="content">
<h4 class="ui center aligned icon header">
......@@ -741,7 +803,7 @@
<div class="content">
<p>
Impossible de créer un type de signalement à partir d'un fichier
GeoJSON de plus de 10Mo (celui importé fait {{ fileSize }} Mo).
GeoJSON de plus de 10Mo (celui importé fait {{ geojsonFileSize > 0 ? geojsonFileSize : csvFileSize }} Mo).
</p>
</div>
<div class="actions">
......@@ -758,6 +820,8 @@
</template>
<script>
import { featureCollection, point } from '@turf/helpers';
import bbox from '@turf/bbox';
import frag from 'vue-frag';
import { mapUtil } from '@/assets/js/map-util.js';
import { mapGetters, mapState, mapActions, mapMutations } from 'vuex';
......@@ -765,7 +829,7 @@ import projectAPI from '@/services/project-api';
import featureTypeAPI from '@/services/featureType-api';
import featureAPI from '@/services/feature-api';
import { fileConvertSizeToMo } from '@/assets/js/utils';
import { fileConvertSizeToMo, csvToJson } from '@/assets/js/utils';
export default {
name: 'ProjectDetails',
......@@ -774,18 +838,6 @@ export default {
frag,
},
filters: {
setDate(value) {
const date = new Date(value);
const d = date.toLocaleDateString('fr', {
year: '2-digit',
month: 'numeric',
day: 'numeric',
});
return d;
},
},
props: {
message: {
type: String,
......@@ -801,7 +853,10 @@ export default {
arraysOfflineErrors: [],
confirmMsg: false,
geojsonImport: [],
fileToImport: { name: '', size: 0 },
csvImport: null,
csvError: null,
geojsonFileToImport: { name: '', size: 0 },
csvFileToImport: { name: '', size: 0 },
slug: this.$route.params.slug,
modalType: false,
is_suscriber: false,
......@@ -854,8 +909,11 @@ export default {
IDGO() {
return this.$store.state.configuration.VUE_APP_IDGO;
},
fileSize() {
return fileConvertSizeToMo(this.fileToImport.size);
geojsonFileSize() {
return fileConvertSizeToMo(this.geojsonFileToImport.size);
},
csvFileSize() {
return fileConvertSizeToMo(this.csvFileToImport.size);
},
isSharedProject() {
return this.$route.path.includes('projet-partage');
......@@ -980,7 +1038,6 @@ export default {
copyLink() {
const sharedLink = window.location.href.replace('projet', 'projet-partage');
navigator.clipboard.writeText(sharedLink).then(()=> {
console.log('success');
this.confirmMsg = true;
}, () => {
console.log('failed');
......@@ -1061,38 +1118,50 @@ export default {
localStorage.setItem('geocontrib_offline', JSON.stringify(arraysOffline));
},
toNewFeatureType() {
toNewGeojsonFeatureType() {
this.featureTypeImporting = true;
this.$router.push({
name: 'ajouter-type-signalement',
params: {
geojson: this.geojsonImport,
fileToImport: this.fileToImport,
fileToImport: this.geojsonFileToImport,
},
});
this.featureTypeImporting = false;
},
toNewCsvFeatureType() {
this.featureTypeImporting = true;
this.$router.push({
name: 'ajouter-type-signalement',
params: {
csv: this.csvImport,
fileToImport: this.csvFileToImport,
},
});
this.featureTypeImporting = false;
},
onFileChange(e) {
onGeoJSONFileChange(e) {
this.featureTypeImporting = true;
var files = e.target.files || e.dataTransfer.files;
if (!files.length) return;
this.fileToImport = files[0];
this.geojsonFileToImport = files[0];
// TODO : VALIDATION IF FILE IS JSON
if (parseFloat(fileConvertSizeToMo(this.fileToImport.size)) > 10) {
if (parseFloat(fileConvertSizeToMo(this.geojsonFileToImport.size)) > 10) {
this.isFileSizeModalOpen = true;
} else if (this.fileToImport.size > 0) {
} else if (this.geojsonFileToImport.size > 0) {
const fr = new FileReader();
try {
fr.onload = (e) => {
this.geojsonImport = JSON.parse(e.target.result);
this.featureTypeImporting = false;
};
fr.readAsText(this.fileToImport);
fr.readAsText(this.geojsonFileToImport);
//* stock filename to import features afterward
this.$store.commit(
'feature_type/SET_FILE_TO_IMPORT',
this.fileToImport
this.geojsonFileToImport
);
} catch (err) {
console.error(err);
......@@ -1103,8 +1172,66 @@ export default {
}
},
onCSVFileChange(e) {
this.featureTypeImporting = true;
var files = e.target.files || e.dataTransfer.files;
if (!files.length) return;
this.csvFileToImport = files[0];
if (parseFloat(fileConvertSizeToMo(this.csvFileToImport.size)) > 10) {
this.isFileSizeModalOpen = true;
} else if (this.csvFileToImport.size > 0) {
const fr = new FileReader();
try {
fr.readAsText(this.csvFileToImport);
fr.onloadend = () => {
// Check if file contains 'lat' and 'long' fields
const headersLine =
fr.result
.split('\n')[0]
.split(',')
.filter(el => {
return el === 'lat' || el === 'lon';
});
// Look for 2 decimal fields in first line of csv
// corresponding to lon and lat
const sampleLine =
fr.result
.split('\n')[1]
.split(',')
.map(el => {
return !isNaN(el) && el.indexOf('.') != -1;
})
.filter(Boolean);
if (sampleLine.length > 1 && headersLine.length === 2) {
this.csvError = null;
this.csvImport = csvToJson(fr.result);
this.featureTypeImporting = false;
//* stock filename to import features afterward
this.$store.commit(
'feature_type/SET_FILE_TO_IMPORT',
this.csvFileToImport
);
} else {
// File doesn't seem to contain coords
this.csvError = `Le fichier ${this.csvFileToImport.name} ne semble pas contenir de coordonnées`;
this.featureTypeImporting = false;
}
};
} catch (err) {
console.error(err);
this.featureTypeImporting = false;
}
} else {
this.featureTypeImporting = false;
}
},
closeFileSizeModal() {
this.fileToImport = { name: '', size: 0 };
this.geojsonFileToImport = { name: '', size: 0 };
this.csvFileToImport = { name: '', size: 0 };
this.featureTypeImporting = false;
this.isFileSizeModalOpen = false;
},
......@@ -1197,32 +1324,27 @@ export default {
this.$store.state.feature_type.feature_types
);
this.mapLoading = false;
this.arraysOffline.forEach((x) => (x.geojson.properties.color = 'red'));
const featuresOffline = this.arraysOffline.map((x) => x.geojson);
this.GET_PROJECT_FEATURES({
project_slug: this.slug,
ordering: '-created_on',
limit: null,
geojson: true,
})
.then(() => {
this.featuresLoading = false;
mapUtil.addFeatures(
[...this.features, ...featuresOffline],
{},
true,
this.$store.state.feature_type.feature_types
);
})
.catch((err) => {
console.error(err);
this.featuresLoading = false;
});
const featuresOffline = this.arraysOffline.map((x) => {
return { ...x.geojson, overideColor: '#ff0000' }; //* red (hex format is better for perf)
});
this.featuresLoading = false;
mapUtil.addFeatures(
featuresOffline,
{},
true,
this.$store.state.feature_type.feature_types,
this.$route.params.slug,
);
featureAPI.getFeaturesBbox(this.slug).then((bbox) => {
if (bbox) {
mapUtil.getMap().fitBounds(bbox, { padding: [25, 25] });
featureAPI.getFeaturesBbox(this.slug).then((featuresBbox) => {
if (featuresBbox) {
if (featuresOffline.length > 0) {//* add offline features to BBOX with Turf
const allFeatures = [...featuresBbox.map((coordinates) => point(coordinates)), ...featuresOffline];
const featureCollect = featureCollection(allFeatures);
const newBbox = bbox(featureCollect);
if (newBbox) featuresBbox = [[newBbox[0], newBbox[1]], [newBbox[2], newBbox[3]]];
}
mapUtil.getMap().fitBounds(featuresBbox, { padding: [25, 25] });
}
});
}
......@@ -1254,7 +1376,7 @@ export default {
margin-right: 5px;
height: 25px;
vertical-align: bottom;
}
}
.feature-type-container {
display: flex;
justify-content: space-between;
......
......@@ -100,8 +100,8 @@
PARAMÈTRES
</div>
<div class="four fields">
<div class="field">
<div class="two fields">
<!-- <div class="field">
<label for="archive_feature">Délai avant archivage</label>
<div class="ui right labeled input">
<input
......@@ -145,7 +145,7 @@
jour(s)
</div>
</div>
</div>
</div> -->
<div class="required field">
<label
for="access_level_pub_feature"
......