Skip to content
Snippets Groups Projects
Project_edit.vue 15.7 KiB
Newer Older
Florent's avatar
Florent committed
    <div
      :class="{active: loading}"
      class="ui inverted dimmer"
    >
      <div class="ui text loader">
        Projet en cours de création. Vous allez être redirigé.
      </div>
    </div>
    <form id="form-project-edit" class="ui form">
      <h1>
        <span v-if="action === 'edit'"
          >Édition du projet "{{ form.title }}"</span
        >
        <span v-else-if="action === 'create'">Création d'un projet</span>
      </h1>

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

      <div class="two fields">
        <div class="required field">
          <label for="title">Titre</label>
          <input
            type="text"
            required
            maxlength="128"
            name="title"
            id="title"
            v-model="form.title"
          />
Timothee P's avatar
Timothee P committed
          <ul id="errorlist-title" class="errorlist">
            <li v-for="error in errors.title" :key="error">
              {{ error }}
            </li>
          </ul>
        </div>
        <div class="field">
          <label>Illustration du projet</label>
          <img
            class="ui small image"
            id="form-input-file-logo"
            :src="
              thumbnailFileSrc
                ? thumbnailFileSrc
                : DJANGO_BASE_URL + form.thumbnail
            "
          />
          <label class="ui icon button" for="thumbnail">
            <i class="file icon"></i>
            <span class="label">{{
              form.thumbnail_name ? form.thumbnail_name : fileToImport.name
            }}</span>
          </label>
          <input
            @change="onFileChange"
            class="file-selection"
            type="file"
            accept="image/jpeg, image/png"
            style="display: none"
            name="thumbnail"
            id="thumbnail"
          />
          <ul
            v-if="errorThumbnail.length"
            id="errorlist-thumbnail"
            class="errorlist"
          >
            <li>
              {{ errorThumbnail[0] }}
            </li>
          </ul>
        </div>
      </div>
      <div class="field">
        <label for="description">Description</label>
        <textarea
          v-model="form.description"
          name="description"
          rows="5"
        ></textarea>
        <!-- {{ form.description.errors }} -->
      </div>

      <div class="ui horizontal divider">PARAMÈTRES</div>

      <div class="four fields">
        <div class="field">
          <label for="archive_feature">Délai avant archivage</label>
          <div class="ui right labeled input">
            <input
              type="number"
              min="0"
Timothee P's avatar
Timothee P committed
              oninput="validity.valid||(value=0);"
              style="padding: 1px 2px"
              name="archive_feature"
              id="archive_feature"
Timothee P's avatar
Timothee P committed
              @blur="checkEmpty"
              v-model="form.archive_feature"
            />
            <div class="ui label">jour(s)</div>
          </div>
          <ul
            v-if="errors_archive_feature.length"
            id="errorlist-achivage"
            class="errorlist"
          >
            <li>
              {{ errors_archive_feature[0] }}
            </li>
          </ul>
        </div>
        <div class="field">
          <label for="delete_feature">Délai avant suppression</label>
          <div class="ui right labeled input">
            <input
              type="number"
              min="0"
Timothee P's avatar
Timothee P committed
              oninput="validity.valid||(value=0);"
              style="padding: 1px 2px"
              name="delete_feature"
              id="delete_feature"
Timothee P's avatar
Timothee P committed
              @blur="checkEmpty"
              v-model="form.delete_feature"
            />
            <div class="ui label">jour(s)</div>
          </div>
        </div>
        <div class="required field">
          <label for="access_level_pub_feature"
            >Visibilité des signalements publiés</label
          >
          <Dropdown
            :options="levelPermissions"
            :selected="form.access_level_pub_feature.name"
            :selection.sync="form.access_level_pub_feature"
          />
Timothee P's avatar
Timothee P committed
          <ul id="errorlist-access_level_pub_feature" class="errorlist">
            <li v-for="error in errors.access_level_pub_feature" :key="error">
              {{ error }}
            </li>
          </ul>
        </div>
        <div class="required field">
          <label for="access_level_arch_feature">
            Visibilité des signalements archivés
          </label>
          <Dropdown
            :options="levelPermissions"
            :selected="form.access_level_arch_feature.name"
            :selection.sync="form.access_level_arch_feature"
          />
Timothee P's avatar
Timothee P committed
          <ul id="errorlist-access_level_arch_feature" class="errorlist">
            <li v-for="error in errors.access_level_arch_feature" :key="error">
              {{ error }}
            </li>
          </ul>
        </div>
      </div>

      <div class="field">
        <div class="ui checkbox">
          <input
            type="checkbox"
            v-model="form.moderation"
            name="moderation"
            id="moderation"
          />
          <label for="moderation">Modération</label>
        </div>
        <!-- {{ form.moderation.errors }} -->
      </div>

      <div class="field">
        <div class="ui checkbox">
          <input
            type="checkbox"
            v-model="form.is_project_type"
            name="is_project_type"
            id="is_project_type"
          />
          <label for="is_project_type">Est un projet type</label>
        </div>
        <!-- {{ form.is_project_type.errors }} -->
      </div>

      <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>
</template>

<script>
const axios = require("axios");
import Dropdown from "@/components/Dropdown.vue";

import { mapGetters } from "vuex";

axios.defaults.headers.common["X-CSRFToken"] = ((name) => {
  var re = new RegExp(name + "=([^;]+)");
  var value = re.exec(document.cookie);
  return value != null ? unescape(value[1]) : null;
})("csrftoken");
export default {
  name: "Project_edit",

  components: {
    Dropdown,
  },

  data() {
    return {
Florent's avatar
Florent committed
      loading: false,
      action: "create",
      levelPermissions: [
        { name: "Utilisateur anonyme", value: "anonymous" },
        { name: "Utilisateur connecté", value: "logged_user" },
        { name: "Contributeur", value: "contributor" },
      ],
      fileToImport: {
        name: "Sélectionner une image ...",
        size: 0,
      },
      errors_archive_feature: [],
Timothee P's avatar
Timothee P committed
      errors: {
        title: [],
        access_level_pub_feature: [],
        access_level_arch_feature: [],
      },
      errorThumbnail: [],
      form: {
        title: "",
        slug: "",
        created_on: "",
        updated_on: "",
        description: "",
        moderation: false,
        thumbnail: "", // todo : utiliser l'image par défaut
        thumbnail_name: "", // todo: delete after getting image in jpg or png instead of data64 (require post to django)
        creator: null,
        access_level_pub_feature: { name: "", value: "" },
        access_level_arch_feature: { name: "", value: "" },
        archive_feature: 0,
        delete_feature: 0,
        nb_features: 0,
        nb_published_features: 0,
        nb_comments: 0,
        nb_published_features_comments: 0,
        nb_contributors: 0,
        is_project_type: false,
      },
      thumbnailFileSrc: "",
    };
  },

  computed: {
    ...mapGetters(["project"]),
Timothee P's avatar
Timothee P committed
    DJANGO_BASE_URL: function () {
      return this.$store.state.configuration.VUE_APP_DJANGO_BASE;
    },
  },

  methods: {
    definePageType() {
      if (this.$router.history.current.name === "project_create") {
        this.action = "create";
      } else if (this.$router.history.current.name === "project_edit") {
        this.action = "edit";
      } else if (this.$router.history.current.name === "project_create_from") {
        this.action = "create_from";
      }
    },
Timothee P's avatar
Timothee P committed
      let ext = n.substring(n.lastIndexOf(".") + 1, n.length).toLowerCase();
      let filename = n.replace("." + ext, "");
      if (filename.length <= len) {
        return n;
      }
      filename = filename.substr(0, len) + (n.length > len ? "[...]" : "");
      return filename + "." + ext;
    },

    validateImgFile(files, handleFile) {
      let url = window.URL || window.webkitURL;
      let image = new Image();
      image.onload = function () {
        handleFile(true);
      };
      image.onerror = function () {
        handleFile(false);
      };
      image.src = url.createObjectURL(files);
      URL.revokeObjectURL(image.src);
    },

    onFileChange(e) {
      // * read image file
      const files = e.target.files || e.dataTransfer.files;

      const _this = this; //* 'this' is different in onload function
      function handleFile(isValid) {
        if (isValid) {
          _this.fileToImport = files[0]; //* store the file to post later
          let reader = new FileReader(); //* read the file to display in the page
          reader.onload = function (e) {
            _this.thumbnailFileSrc = e.target.result;
          };
          reader.readAsDataURL(_this.fileToImport);
          _this.errorThumbnail = [];
        } else {
          _this.errorThumbnail.push(
            "Transférez une image valide. Le fichier que vous avez transféré n'est pas une image, ou il est corrompu."
          );
        }
      }

      if (files.length) {
        //* check if file is an image and pass callback to handle file
        this.validateImgFile(files[0], handleFile);
      }
Timothee P's avatar
Timothee P committed
    checkEmpty() {
      //* forbid empty fields
      if (!this.form.archive_feature) this.form.archive_feature = 0;
      if (!this.form.delete_feature) this.form.delete_feature = 0;
    },

      Promise.all([
        this.$store.dispatch("GET_USER_LEVEL_PERMISSIONS"), //* refresh projects permissions
        this.$store.dispatch("GET_ALL_PROJECTS"), //* & refresh project list
      ]).then(() =>
        // * go back to project list
        this.$router.push({
      );
    },

    postProjectThumbnail(projectSlug) {
      //* send img to the backend when feature_type is created
      if (this.isValidImage(this.fileToImport)) {
        let formData = new FormData();
        formData.append("file", this.fileToImport);
        const url =
          this.$store.state.configuration.VUE_APP_DJANGO_API_BASE +
          "projects/" +
          projectSlug +
          "/thumbnail/";
        return axios
          .put(url, formData, {
            headers: {
              "Content-Type": "multipart/form-data",
            },
          })
          .then((response) => {
            if (response && response.status === 200) {
              this.goBackNrefresh(projectSlug);
            }
          })
          .catch((error) => {
            let err_msg =
              "Transférez une image valide. Le fichier que vous avez transféré n'est pas une image, ou il est corrompu.";
            if (error.response.data[0]) err_msg = error.response.data[0];
            this.errorThumbnail.push(err_msg);
            throw error;
          });
      }
Timothee P's avatar
Timothee P committed
    checkForm() {
      if (this.form.archive_feature > this.form.delete_feature) {
        this.errors_archive_feature.push(
          "Le délais de suppression doit être supérieur au délais d'archivage."
        );
        return false;
      }
Timothee P's avatar
Timothee P committed
      for (const key in this.errors) {
        if ((key === "title" && this.form[key]) || this.form[key].value) {
          this.errors[key] = [];
        } else if (!this.errors[key].length) {
          this.errors[key].push(
            key === "title"
              ? "Veuillez compléter ce champ."
              : "Sélectionnez un choix valide. Ce choix ne fait pas partie de ceux disponibles."
          );
          document
            .getElementById(`errorlist-${key}`)
            .scrollIntoView({ block: "end", inline: "nearest" });
          return false;
        }
      }
      return true;
    },

Timothee P's avatar
Timothee P committed
      if (!this.checkForm()) return;
      const projectData = {
        title: this.form.title,
        description: this.form.description,
        access_level_arch_feature: this.form.access_level_arch_feature.value,
        access_level_pub_feature: this.form.access_level_pub_feature.value,
        archive_feature: this.form.archive_feature,
        delete_feature: this.form.delete_feature,
        is_project_type: this.form.is_project_type,
        moderation: this.form.moderation,
      };

      if (this.action === "create" || this.action === "duplicate") {
Florent's avatar
Florent committed
        this.loading = true;
Timothee P's avatar
Timothee P committed
          .post(
            `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/`,
            projectData
          )
          .then((response) => {
            if (response && response.status === 201 && response.data) {
              //* send thumbnail after feature_type was created
Timothee P's avatar
Timothee P committed
              if (this.fileToImport.size > 0) {
                this.postProjectThumbnail(response.data.slug);
Timothee P's avatar
Timothee P committed
              } else {
                this.goBackNrefresh(response.data.slug);
              }
Florent's avatar
Florent committed
            this.loading = false;
Timothee P's avatar
Timothee P committed
            if (error.response.data.title[0]) {
              this.errors.title.push(error.response.data.title[0]);
            }
Florent's avatar
Florent committed
            this.loading = false;
            throw error;
          });
      } else if (this.action === "edit") {
        await axios
          .put(
            `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.project.slug}/`,
            projectData
          )
          .then((response) => {
            if (response && response.status === 200) {
              //* send thumbnail after feature_type was updated
              if (this.fileToImport.size > 0) {
                this.postProjectThumbnail(this.project.slug);
              } else {
                this.goBackNrefresh(this.project.slug);
              }
Timothee P's avatar
Timothee P committed
            if (error.response.data.title[0]) {
              this.errors.title.push(error.response.data.title[0]);
            }
    if (this.action === "create") {
      this.thumbnailFileSrc = require("@/assets/img/default.png");
    } else if (this.action === "edit" || this.action === "create_from") {
      if (!this.project) {
        this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug);
      }
      this.form = { ...this.project }; //* create a new object to avoid modifying original one
      if (this.action === "create_from") {
        this.form.title =
          this.project.title +
          ` (Copie-${new Date()
            .toLocaleString()
            .slice(0, -3)
            .replace(",", "")})`;
        this.form.is_project_type = false;
      }
      //* transform string values to objects for dropdowns display (could be in a computed)
      this.form.access_level_pub_feature = {
        name: this.project.access_level_pub_feature,
        value: this.levelPermissions.find(
          (el) => (el.name = this.project.access_level_pub_feature)
        ).value,
      };
      this.form.access_level_arch_feature = {
        name: this.project.access_level_arch_feature,
        value: this.levelPermissions.find(
          (el) => (el.name = this.project.access_level_arch_feature)
        ).value,
      };
    }
  },
};
</script>

<style media="screen">
#form-input-file-logo {
  margin-left: auto;
  margin-right: auto;
}

.close.icon:hover {
  cursor: pointer;
}
</style>