Skip to content
Snippets Groups Projects
Feature_edit.vue 23.4 KiB
Newer Older
<template>
  <div v-frag>
    <div class="fourteen wide column">
DESPRES Damien's avatar
DESPRES Damien committed
        v-if="feature && currentRouteName === 'editer-signalement'"
Timothee P's avatar
Timothee P committed
        Mise à jour du signalement "{{ feature.title || feature.feature_id }}"
DESPRES Damien's avatar
DESPRES Damien committed
          feature_type && currentRouteName === 'ajouter-signalement'
        Création d'un signalement <small>[{{ feature_type.title }}]</small>
      </h1>

      <form
        id="form-feature-edit"
        action=""
        method="post"
        enctype="multipart/form-data"
        class="ui form"
      >
        <!-- Feature Fields -->
        <div class="two fields">
          <div class="required field">
            <label :for="form.title.id_for_label">{{ form.title.label }}</label>
              :maxlength="form.title.field.max_length"
              :name="form.title.html_name"
              :id="form.title.id_for_label"
              v-model="form.title.value"
            {{ form.title.errors }}
            <label :for="form.status.id_for_label">{{
              form.status.label
              :options="form.status.field.choices"
              :selected="selected_status"
              :selection.sync="selected_status"
            {{ form.status.errors }}
          <label :for="form.description.id_for_label">{{
            form.description.label
            :name="form.description.html_name"
            v-model="form.description.value"
          {{ form.description.errors }}
          <label :for="form.geom.id_for_label">{{ form.geom.label }}</label>
          <div v-frag v-if="feature_type && feature_type.geom_type === 'point'">
            <p>
              <button
                id="add-geo-image"
                type="button"
                class="ui compact button"
              >
                <i class="file image icon"></i>Importer une image géoréférencée
              </button>
              Vous pouvez utiliser une image géoréférencée pour localiser le
              signalement.
            </p>
            <p>
              <button
                id="create-point-geoposition"
                type="button"
                class="ui compact button"
              >
                <i class="ui map marker alternate icon"></i>Positionner le
                signalement à partir de votre géolocalisation
              </button>
            </p>
            <span id="erreur-geolocalisation" style="display: none">
              <div class="ui negative message">
                <div class="header">
                  Une erreur est survenue avec la fonctionnalité de
                  géolocalisation
                </div>
                <p id="erreur-geolocalisation-message"></p>
          {{ form.geom.errors }}
            :name="form.geom.html_name"
            :id="form.geom.id_for_label"
            v-model="form.geom.value"
          />
          <div class="ui tab active map-container" data-tab="map">
            <div id="map"></div>
            <!-- // todo: ajouter v-if -->
            <!-- {% if serialized_base_maps|length > 0 %} {% include
            "geocontrib/map-layers/sidebar-layers.html" with
            basemaps=serialized_base_maps layers=serialized_layers
            project=project.slug%} {% endif %} -->
          </div>
        </div>

        <!-- Extra Fields -->
        <div class="ui horizontal divider">DONNÉES MÉTIER</div>
        <!-- // Todo: Récupérer les "extra_form" de l'API -->
        <div
          v-for="(field, index) in extra_form_with_values"
          :key="field.field_type + index"
          class="field"
        >
          <div v-frag v-if="field.field_type === 'char'">
            <label for="field.name">{{ field.label }}</label>
              type="text"
              :name="field.name"
              :id="field.name"
              v-model="field.value"
              @blur="updateStore_extra_form"
          </div>

          <div v-frag v-else-if="field.field_type === 'list'">
            <label for="field.name">{{ field.label }}</label>
            <Dropdown
              :options="field.choices"
              :selected="selected_extra_form_list"
              :selection.sync="selected_extra_form_list"
            />
          </div>
          <div v-frag v-else-if="field.field_type === 'integer'">
            <label for="field.name">{{ field.label }}</label>
            <div class="ui input">
              <!-- //* si click sur fléche dans champ input, pas de focus, donc pas de blur, donc utilisation de @change -->
              <input
                type="number"
                :name="field.name"
                :id="field.name"
                v-model.number="field.value"
                @change="updateStore_extra_form"
              />
            </div>
          </div>
          <div v-frag v-else-if="field.field_type === 'boolean'">
            <div class="ui checkbox">
              <input
                type="checkbox"
                :checked="field.value"
                :name="field.name"
                :id="field.name"
                @change="updateStore_extra_form"
              />
              <label for="field.name">{{ field.label }}</label>
          <div v-frag v-else-if="field.field_type === 'date'">
            <label for="field.name">{{ field.label }}</label>
              type="date"
              :name="field.name"
              :id="field.name"
              v-model="field.value"
              @blur="updateStore_extra_form"
          <div v-frag v-else-if="field.field_type === 'decimal'">
            <label for="field.name">{{ field.label }}</label>
            <div class="ui input">
              <input
                type="number"
                step=".01"
                :name="field.name"
                :id="field.name"
                v-model.number="field.value"
                @change="updateStore_extra_form"
              />
            </div>
          <div v-frag v-else-if="field.field_type === 'text'">
            <label :for="field.name">{{ field.label }}</label>
            <textarea
              rows="3"
              @blur="updateStore_extra_form"
            ></textarea>
          </div>
          {{ field.errors }}
        </div>

        <!-- Pièces jointes -->
        <div class="ui horizontal divider">PIÈCES JOINTES</div>
        <!-- {{ attachment_formset.non_form_errors }} -->
          <!-- {{ attachment_formset.management_form }} -->
          <FeatureAttachmentForm
            v-for="form in attachmentFormset"
            :key="form.dataKey"
            :attachmentForm="form"
          />
          @click="add_attachement_formset"
          id="add-attachment"
          type="button"
          class="ui compact basic button button-hover-green"
        >
          <i class="ui plus icon"></i>Ajouter une pièce jointe
        </button>

        <!-- Signalements liés -->
        <div class="ui horizontal divider">SIGNALEMENTS LIÉS</div>
        <!-- {{ linked_formset.non_form_errors }} -->
          <!-- {{ linked_formset.management_form }} -->

          <FeatureLinkedForm
            v-for="form in linkedFormset"
            :key="form.dataKey"
            :linkedForm="form"
            :features="features"
          />
          @click="add_linked_formset"
          id="add-link"
          type="button"
          class="ui compact basic button button-hover-green"
        >
          <i class="ui plus icon"></i>Ajouter une liaison
        </button>

        <div class="ui divider"></div>

        <button @click="postForm" type="button" class="ui teal icon button">
          <i class="white save icon"></i> Enregistrer les changements
        </button>
      </form>
    </div>
  </div>
</template>

<script>
import frag from "vue-frag";
import { mapGetters, mapState } from "vuex";
import FeatureAttachmentForm from "@/components/feature/FeatureAttachmentForm";
import FeatureLinkedForm from "@/components/feature/FeatureLinkedForm";
import Dropdown from "@/components/Dropdown.vue";
import SidebarLayers from "@/components/map-layers/SidebarLayers";
DESPRES Damien's avatar
DESPRES Damien committed
import L from "leaflet";
import "leaflet-draw";
import { mapUtil } from "@/assets/js/map-util.js";
const axios = require("axios");
DESPRES Damien's avatar
DESPRES Damien committed
import flip from '@turf/flip'

export default {
  name: "Feature_edit",
  directives: {
    frag,
  },

  components: {
    FeatureAttachmentForm,
    FeatureLinkedForm,
  },

  computed: {
    ...mapState(["project"]),
    ...mapState("feature", [
      "attachmentFormset",
      "linkedFormset",
      "features",
      "extra_form",
    ]),
    ...mapGetters("feature_type", ["feature_type"]),
DESPRES Damien's avatar
DESPRES Damien committed
    currentRouteName() {
        return this.$route.name;
    },
Timothee P's avatar
Timothee P committed
    feature: function () {
      return this.$store.state.feature.features.find(
        (el) => el.feature_id === this.$route.params.slug_signal
        return this.form.status.value;
        this.form.status.value = newValue;
Timothee P's avatar
Timothee P committed
    extra_form_with_values: function () {
      return this.extra_form.map((el) => {
        return { ...el, value: el.value ? el.value : null };
      });
    },
    selected_extra_form_list: {
      get() {
        return this.extra_form_with_values.find(
          (el) => el.field_type === "list"
        ).value;
      },
      set(newValue) {
        const index = this.extra_form_with_values.findIndex(
          (el) => el.field_type === "list"
        );
        this.extra_form_with_values[index].value = newValue;
        this.updateStore_extra_form();
      },
    },
DESPRES Damien's avatar
DESPRES Damien committed
  watch: {
    feature_type(newValue) {
      console.log(newValue)
      this.onFeatureTypeLoaded();
    },
  },
      attachmentDataKey: 0,
      linkedDataKey: 0,
      form: {
          errors: null,
          id_for_label: "name",
          html_name: "name",
          label: "Nom",
          value: "",
          errors: null,
          id_for_label: "status",
            choices: ["Brouillon", "Publié", "Archivé"],
          html_name: "status",
          label: "Statut",
          value: "Brouillon",
          errors: null,
          id_for_label: "description",
          html_name: "description",
          label: "Description",
          value: "",
          label: "Localisation",
    add_attachement_formset() {
      this.$store.commit("feature/ADD_ATTACHMENT_FORM", this.attachmentDataKey); // * create an object with the counter in store
      this.attachmentDataKey += 1; // * increment counter for key in v-for
    },
    add_linked_formset() {
      this.$store.commit("feature/ADD_LINKED_FORM", this.linkedDataKey); // * create an object with the counter in store
      this.linkedDataKey += 1; // * increment counter for key in v-for
    updateStore() {
      this.$store.commit("feature/UPDATE_FORM", {
        title: this.form.title.value,
        status: this.form.status.value,
        description: this.form.description,
    updateStore_extra_form() {
      this.$store.commit(
        "feature/UPDATE_EXTRA_FORM",
        this.extra_form_with_values
      );
    },

    postForm() {
      if (this.form.title.value) {
        this.form.title.errors = null;
        this.$store.dispatch("feature/POST_FEATURE");
      } else {
        this.form.title.errors = "Veuillez compléter ce champ.";
      }
    },
DESPRES Damien's avatar
DESPRES Damien committed
    onFeatureTypeLoaded(){
DESPRES Damien's avatar
DESPRES Damien committed
      var geomLeaflet = {
        'point': 'circlemarker',
        'linestring': 'polyline',
        'polygon': 'polygon'
      }
DESPRES Damien's avatar
DESPRES Damien committed
      console.log(this.feature_type)
      var geomType = this.feature_type.geom_type;
DESPRES Damien's avatar
DESPRES Damien committed
      var drawConfig = {
        polygon: false,
        marker: false,
        polyline: false,
        rectangle: false,
        circle: false,
        circlemarker: false,
      }
      drawConfig[geomLeaflet[geomType]] = true

      L.drawLocal = {
        draw: {
          toolbar: {
            actions: {
              title: 'Annuler le dessin',
              text: 'Annuler'
            },
            finish: {
              title: 'Terminer le dessin',
              text: 'Terminer'
            },
            undo: {
              title: 'Supprimer le dernier point dessiné',
              text: 'Supprimer le dernier point'
            },
            buttons: {
              polyline: 'Dessiner une polyligne',
              polygon: 'Dessiner un polygone',
              rectangle: 'Dessiner un rectangle',
              circle: 'Dessiner un cercle',
              marker: 'Dessiner une balise',
              circlemarker: 'Dessiner un point'
            }
          },
          handlers: {
            circle: {
              tooltip: {
                start: 'Cliquer et glisser pour dessiner le cercle.'
              },
              radius: 'Rayon'
            },
            circlemarker: {
              tooltip: {
                start: 'Cliquer sur la carte pour placer le point.'
              }
            },
            marker: {
              tooltip: {
                start: 'Cliquer sur la carte pour placer la balise.'
              }
            },
            polygon: {
              tooltip: {
                start: 'Cliquer pour commencer à dessiner.',
                cont: 'Cliquer pour continuer à dessiner.',
                end: 'Cliquer sur le premier point pour terminer le dessin.'
              }
            },
            polyline: {
              error: '<strong>Error:</strong> shape edges cannot cross!',
              tooltip: {
                start: 'Cliquer pour commencer à dessiner.',
                cont: 'Cliquer pour continuer à dessiner.',
                end: 'Cliquer sur le dernier point pour terminer le dessin.'
              }
            },
            rectangle: {
              tooltip: {
                start: 'Cliquer et glisser pour dessiner le rectangle.'
              }
            },
            simpleshape: {
              tooltip: {
                end: 'Relâcher la souris pour terminer de dessiner.'
              }
            }
          }
        },
        edit: {
          toolbar: {
            actions: {
              save: {
                title: 'Sauver les modifications',
                text: 'Sauver'
              },
              cancel: {
                title: 'Annuler la modification, annule toutes les modifications',
                text: 'Annuler'
              },
              clearAll: {
                title: 'Effacer l\'objet',
                text: 'Effacer'
              }
            },
            buttons: {
              edit: 'Modifier l\'objet',
              editDisabled: 'Aucun objet à modifier',
              remove: 'Supprimer l\'objet',
              removeDisabled: 'Aucun objet à supprimer'
            }
          },
          handlers: {
            edit: {
              tooltip: {
                text: 'Faites glisser les marqueurs ou les balises pour modifier l\'élément.',
                subtext: 'Cliquez sur Annuler pour annuler les modifications..'
              }
            },
            remove: {
              tooltip: {
                text: 'Cliquez sur un élément pour le supprimer.'
              }
            }
          }
        }
      };


DESPRES Damien's avatar
DESPRES Damien committed
      this.drawnItems = new L.FeatureGroup();
      this.map.addLayer(this.drawnItems);

      this.drawControlFull = new L.Control.Draw({
        position: 'topright',
        edit: {
          featureGroup: this.drawnItems
        },
        draw: drawConfig,
      })

      this.drawControlEditOnly = new L.Control.Draw({
        position: 'topright',
        edit: {
          featureGroup: this.drawnItems
        },
        draw: false
      });

      if (this.currentRouteName === "editer-signalement") {
        
        this.map.addControl(this.drawControlEditOnly);
      }
      else
        this.map.addControl(this.drawControlFull);


      this.map.on("draw:created", (function (e) {
        var layer = e.layer
        this.add_layer_call_back(layer)
      }).bind(this))
      //var wellknown;// TODO Remplacer par autre chose

      this.map.on("draw:edited", (function(e) {
        var layers = e.layers;
        let self=this;
        layers.eachLayer(function (layer) {
          //this.updateGeomField(wellknown.stringify(layer.toGeoJSON()))
          self.updateGeomField(layer.toGeoJSON())
        })
      }).bind(this));

      this.map.on("draw:deleted", (function() {
        this.drawControlEditOnly.remove(this.map)
        this.drawControlFull.addTo(this.map)
        this.updateGeomField('')
        if(geomType==="point"){
          document.getElementById('create-point-geoposition').style.display = "inline"
          document.getElementById('erreur-geolocalisation').style.display = "none"
        }
      }).bind(this));
    },
    updateMap(map, geomFeatureJSON) {
      this.drawnItems.clearLayers();
      console.log("update map")
      var geomType = this.feature_type.geom_type;
      if (geomFeatureJSON) {
        var geomJSON = flip(geomFeatureJSON.geometry);//turf.flip(geomFeatureJSON)
        if (geomType === 'point') {
          L.circleMarker(geomJSON.coordinates).addTo(this.drawnItems)
        } else if (geomType === 'linestring') {
          L.polyline(geomJSON.coordinates).addTo(this.drawnItems)
        } else if (geomType === 'polygon') {
          L.polygon(geomJSON.coordinates).addTo(this.drawnItems)
        }
        this.map.fitBounds(this.drawnItems.getBounds())
      } else {
        this.map.setView(this.$store.state.configuration.DEFAULT_MAP_VIEW.center,this.$store.state.configuration.DEFAULT_MAP_VIEW.zoom)
      }
    },

    updateGeomField(newGeom){
      this.geometry=newGeom;
    },
    initMap(){
      
DESPRES Damien's avatar
DESPRES Damien committed
      //console.log(drawnItems);
      //console.log(configuration);
DESPRES Damien's avatar
DESPRES Damien committed
      var mapDefaultViewCenter = this.$store.state.configuration.DEFAULT_MAP_VIEW.center;
      var mapDefaultViewZoom = this.$store.state.configuration.DEFAULT_MAP_VIEW.zoom;
DESPRES Damien's avatar
DESPRES Damien committed

      // Create the map, then init the layers and features
      this.map = mapUtil.createMap({
          mapDefaultViewCenter,
          mapDefaultViewZoom
        });

      // mapUtil.addLayers(layers, serviceMap, optionsMap);
DESPRES Damien's avatar
DESPRES Damien committed
      const currentFeatureId=this.$route.params.slug_signal;

      const url=`${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature/?output=geojson`;
DESPRES Damien's avatar
DESPRES Damien committed
      axios.get(url)
        .then((response) => {
          const features = response.data.features;
          if (features) {
            const allFeaturesExceptCurrent = features.filter(feat => feat.id !== currentFeatureId);
            mapUtil.addFeatures(allFeaturesExceptCurrent);
DESPRES Damien's avatar
DESPRES Damien committed
            if (this.currentRouteName === "editer-signalement") {
              const currentFeature = features.filter(feat => feat.id === currentFeatureId)[0];
              this.updateMap(this.map,currentFeature)
            }
DESPRES Damien's avatar
DESPRES Damien committed
          }
        })
        .catch((error) => {
          throw error;
        });
      
      let self=this;
DESPRES Damien's avatar
DESPRES Damien committed
      // ------   Listen Sidebar events ---------- //
      // Listen custom events triggered by the sidebar-layers
      document.addEventListener('add-layers', (event) => {
        mapUtil.removeLayers(self.map);
        // Reverse is done because the first layer in order has to be added in the map in last.
        // Slice is done because reverse() changes the original array, so we make a copy first
        mapUtil.addLayers(event.detail.slice().reverse(), this.$store.state.configuration.DEFAULT_BASE_MAP.SERVICE, this.$store.state.configuration.DEFAULT_BASE_MAP.OPTIONS);
      });
DESPRES Damien's avatar
DESPRES Damien committed
      document.addEventListener('update-opacity', (event) => {
        mapUtil.updateOpacity(event.detail.layerId, event.detail.opacity);
      });
DESPRES Damien's avatar
DESPRES Damien committed
      document.addEventListener('change-layers-order', (event) => {
        // Reverse is done because the first layer in order has to be added in the map in last.
        // Slice is done because reverse() changes the original array, so we make a copy first
        mapUtil.updateOrder(event.detail.layers.slice().reverse());
      });
DESPRES Damien's avatar
DESPRES Damien committed
      // --------- End sidebar events ----------
      // Check if at least one basemap exist. If not, use the default application
        const basemaps = undefined;//JSON.parse(document.getElementById('basemaps').textContent);
        if (!basemaps || basemaps && basemaps.length === 0) {
          mapUtil.addLayers(null, this.$store.state.configuration.DEFAULT_BASE_MAP.SERVICE, this.$store.state.configuration.DEFAULT_BASE_MAP.OPTIONS);
        }
      
    },
DESPRES Damien's avatar
DESPRES Damien committed
    add_layer_call_back(layer){
      layer.addTo(this.drawnItems)
      this.drawControlFull.remove(this.map)
      this.drawControlEditOnly.addTo(this.map)
      //var wellknown;// TODO Remplacer par autre chose
      //this.updateGeomField(wellknown.stringify(layer.toGeoJSON()))
      this.updateGeomField(layer.toGeoJSON())
      if(this.feature_type.geomType==="point"){
        document.getElementById('create-point-geoposition').style.display = "none"
        document.getElementById('erreur-geolocalisation').style.display = "none"
      }
  created() {
    if (!this.project) {
      this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug);
DESPRES Damien's avatar
DESPRES Damien committed
    console.log("SET_CURRENT_FEATURE_TYPE_SLUG")
Timothee P's avatar
Timothee P committed
    this.$store.commit(
      "feature_type/SET_CURRENT_FEATURE_TYPE_SLUG",
      this.$route.params.slug_type_signal
    );
DESPRES Damien's avatar
DESPRES Damien committed
    console.log(this.currentRouteName)
    console.log(this.$route.params.slug_type_signal)

    console.log(this.$store.state.feature_type.feature_types.length)
    console.log(this.$store.state.feature_type.current_feature_type_slug)
    console.log(this.$store.state.feature_type.feature_type)
    console.log(this.feature_type)
    if (this.currentRouteName === "editer-signalement") {
Timothee P's avatar
Timothee P committed
      for (let el in this.feature) {
        if (el && this.form[el]) this.form[el].value = this.feature[el];
      }
    }
DESPRES Damien's avatar
DESPRES Damien committed
    this.initMap();
   
};

// TODO : add script from django and convert:
</script>

<style>
#map {
  height: 70vh;
  width: 100%;

@media only screen and (max-width: 767px) {
  #map {
    height: 80vh;
  }
}
/* // ! missing style in semantic.min.css, je ne comprends pas comment... */
.ui.right.floated.button {
  float: right;
  margin-right: 0;
  margin-left: 0.25em;
}
/* // ! margin écrasé par class last-child first-child, pas normal ... */
.ui.segment {
  margin: 1rem 0 !important;
}