view client/src/components/systemconfiguration/MorphologyClassbreaks.vue @ 5736:55892008ec96 default tip

Fixed a bunch of corner cases in WG import.
author Sascha Wilde <wilde@sha-bang.de>
date Wed, 29 May 2024 19:02:42 +0200
parents 84d01a536bec
children
line wrap: on
line source

<template>
  <div class="d-flex flex-column py-4">
    <div class="px-3">
      <h6 class="font-weight-bold"><translate>Sounding Result</translate></h6>
      <div class="d-flex flex-wrap">
        <div
          class="input-group mb-3 mr-2 classbreak"
          v-for="(value, i) in morphologyClassbreaks"
          :key="i"
        >
          <div class="input-group-prepend">
            <button
              :class="[
                'btn btn-sm btn-outline-secondary',
                { hasColor: morphologyClassbreaks[i][1] !== '#ffffff' }
              ]"
              :style="
                'width: 28px; background-color: ' +
                  (morphologyClassbreaks[i][1] || 'transparent')
              "
              type="button"
              @click="showColorPicker('sounding-' + i)"
            >
              <font-awesome-icon icon="tint" />
            </button>
            <div
              class="color-picker card shadow-sm"
              v-if="activeColorPicker === 'sounding-' + i"
            >
              <UIBoxHeader
                :title="colorPickerTitle"
                icon="paint-brush"
                :actions="[
                  {
                    callback: () => {
                      morphologyClassbreaks[i][1] = '#ffffff';
                      activeColorPicker = null;
                    },
                    icon: 'trash'
                  },
                  {
                    callback: () => {
                      activeColorPicker = null;
                    },
                    icon: 'check'
                  }
                ]"
              />
              <chrome-picker
                v-model="morphologyClassbreaks[i][1]"
                @input="color => (morphologyClassbreaks[i][1] = color.hex)"
              />
            </div>
          </div>
          <form id="novalidatedform" />
          <input
            v-model.number="morphologyClassbreaks[i][0]"
            type="number"
            min="0"
            step="0.01"
            class="form-control form-control-sm numfield"
          />
          <div class="input-group-append">
            <button
              class="btn btn-sm btn-outline-secondary"
              type="button"
              @click="morphologyClassbreaks.splice(i, 1)"
            >
              <font-awesome-icon icon="times" />
            </button>
            <button
              class="btn btn-sm btn-outline-secondary"
              @click="addClassbreak(morphologyClassbreaks, i)"
            >
              <font-awesome-icon icon="plus" />
            </button>
          </div>
        </div>
        <button
          v-if="!morphologyClassbreaks.length"
          class="btn btn-sm btn-success mb-3"
          @click="addClassbreak(morphologyClassbreaks, 0)"
        >
          <font-awesome-icon icon="plus" />
        </button>
      </div>
    </div>
    <div class="mb-4 px-3">
      <a
        @click.prevent="submitClassbreaks"
        :class="[
          'btn btn-info btn-sm text-white',
          { disabled: !checkClassbreaks }
        ]"
      >
        <translate>Send</translate>
      </a>
      <a
        @click.prevent="resetClassbreaks"
        class="btn btn-outline-info btn-sm ml-2"
      >
        <translate>Reset to defaults</translate>
      </a>
      <span class="text-danger" v-if="!checkClassbreaks">
        {{ validationMessage }}
      </span>
      <span class="text-secondary fix-trans-space" v-translate>
        Changes need a map reload. Consider informing your users.
      </span>
    </div>
    <div class="px-3">
      <h6 class="font-weight-bold">
        <translate>Sounding Result Comparison</translate>
      </h6>
      <div class="d-flex flex-wrap">
        <div
          class="input-group mb-3 mr-2 classbreak"
          v-for="(value, i) in morphologyClassbreaksCompare"
          :key="i"
        >
          <div class="input-group-prepend">
            <button
              :class="[
                'btn btn-sm btn-outline-secondary',
                { hasColor: morphologyClassbreaksCompare[i][1] !== '#ffffff' }
              ]"
              :style="
                'width: 28px; background-color: ' +
                  (morphologyClassbreaksCompare[i][1] || 'transparent')
              "
              type="button"
              @click="showColorPicker('compare-' + i)"
            >
              <font-awesome-icon icon="tint" />
            </button>
            <div
              class="color-picker card shadow-sm"
              v-if="activeColorPicker === 'compare-' + i"
            >
              <UIBoxHeader
                :title="colorPickerTitle"
                icon="paint-brush"
                :actions="[
                  {
                    callback: () => {
                      morphologyClassbreaksCompare[i][1] = '#ffffff';
                      activeColorPicker = null;
                    },
                    icon: 'trash'
                  },
                  {
                    callback: () => {
                      activeColorPicker = null;
                    },
                    icon: 'check'
                  }
                ]"
              />
              <chrome-picker
                v-model="morphologyClassbreaksCompare[i][1]"
                @input="
                  color => (morphologyClassbreaksCompare[i][1] = color.hex)
                "
              />
            </div>
          </div>
          <input
            v-model.number="morphologyClassbreaksCompare[i][0]"
            type="number"
            step="0.01"
            class="form-control form-control-sm numfield"
          />
          <div class="input-group-append">
            <button
              class="btn btn-sm btn-outline-secondary"
              type="button"
              @click="morphologyClassbreaksCompare.splice(i, 1)"
            >
              <font-awesome-icon icon="times" />
            </button>
            <button
              class="btn btn-sm btn-outline-secondary"
              @click="addClassbreak(morphologyClassbreaksCompare, i)"
            >
              <font-awesome-icon icon="plus" />
            </button>
          </div>
        </div>
        <button
          v-if="!morphologyClassbreaksCompare.length"
          class="btn btn-sm btn-success mb-3"
          @click="addClassbreak(morphologyClassbreaksCompare, 0)"
        >
          <font-awesome-icon icon="plus" />
        </button>
      </div>
    </div>
    <div class="px-3">
      <a
        @click.prevent="submitClassbreaksCompare"
        :class="[
          'btn btn-info btn-sm text-white',
          { disabled: !checkClassbreaksCompare }
        ]"
      >
        <translate>Send</translate>
      </a>
      <a
        @click.prevent="resetClassbreaksCompare"
        class="btn btn-outline-info btn-sm ml-2"
      >
        <translate>Reset to defaults</translate>
      </a>
      <span class="text-danger" v-if="!checkClassbreaksCompare">
        {{ validationMessageForCompare }}
      </span>
      <span class="text-secondary fix-trans-space" v-translate>
        Colour changes need a map reload. Value changes need a de- and re-select
        of a difference calculation. Inform your users!
      </span>
    </div>
  </div>
</template>

<style scoped>
.numfield:invalid {
  border: 2px solid;
  border-color: #ff0000;
}

.classbreak {
  width: 154px;
}
.classbreak .btn-outline-secondary {
  border-color: #ccc;
  color: #ccc;
}
.classbreak .btn-outline-secondary:hover:not(.hasColor) {
  background: #eee !important;
}
.classbreak .input-group-prepend .btn-outline-secondary.hasColor {
  color: rgba(255, 255, 255, 0.5);
}
.classbreak .input-group-append .btn-outline-secondary:hover {
  color: #dc3545;
}
.classbreak .input-group-append .btn-outline-secondary:last-child:hover {
  color: #28a745;
}
.classbreak .color-picker {
  position: absolute;
  top: -4px;
  left: 19px;
  z-index: 9;
  overflow: hidden;
  border-top-left-radius: 0 !important;
}
.classbreak .color-picker .btn {
  border-radius: 0 !important;
}
.classbreak .color-picker .vc-chrome {
  box-shadow: none;
}

::v-deep .vc-chrome-alpha-wrap {
  display: none !important;
}

::v-deep .vc-chrome-hue-wrap {
  margin-top: 10px;
}

::v-deep .vc-chrome-saturation-wrap {
  border-radius: 0;
}
</style>

<script>
/* This is Free Software under GNU Affero General Public License v >= 3.0
 * without warranty, see README.md and license for details.
 *
 * SPDX-License-Identifier: AGPL-3.0-or-later
 * License-Filename: LICENSES/AGPL-3.0.txt
 *
 * Copyright (C) 2018 by via donau
 *   – Österreichische Wasserstraßen-Gesellschaft mbH
 * Software engineering by Intevation GmbH
 *
 * Author(s):
 * Markus Kottländer <markus@intevation.de>
 */
import { mapState } from "vuex";
import { Chrome } from "vue-color";
import defaults from "./defaults";

export default {
  components: {
    "chrome-picker": Chrome
  },
  data() {
    return {
      morphologyClassbreaks: [],
      morphologyClassbreaksCompare: [],
      activeColorPicker: null,
      closeColorPickerListener: null,
      validationMessage: "",
      validationMessageForCompare: ""
    };
  },
  computed: {
    ...mapState("application", ["config"]),
    colorPickerTitle() {
      return this.$gettext("Choose color");
    },
    checkClassbreaks() {
      return (
        this.valuesAreValid(this.morphologyClassbreaks) &&
        !this.hasDoublettes(this.morphologyClassbreaks)
      );
    },
    checkClassbreaksCompare() {
      return (
        this.valuesAreValid(this.morphologyClassbreaksCompare, "compare") &&
        !this.hasDoublettes(this.morphologyClassbreaksCompare, "compare")
      );
    }
  },
  methods: {
    // check if the same value is used for more than one field.
    hasDoublettes(m, compare) {
      const errorMessage = this.$gettext(
        "Same value is used in multiple fields."
      );
      let values = [];
      for (let i = 0; i < m.length; i++) {
        values[i] = Number(m[i][0]);
      }
      if (new Set(values).size !== values.length) {
        //  determine which message to change
        if (compare !== "compare") {
          this.validationMessage = errorMessage;
        } else {
          this.validationMessageForCompare = errorMessage;
        }
        return true;
      }
      return false;
    },
    valuesAreValid(m, compare) {
      const errorMessage = this.$gettext(
        "There are invalid classbreak values."
      );
      let values = m.map(e => {
        const element = e[0];
        if (!isNaN(element)) {
          if (element === "") return false; // check if the field is empty
          if (!isNaN(element)) {
            const numberParts = String(element).split("."); // check number of decimal places
            return numberParts.length == 2 ? numberParts[1].length < 3 : true;
          }
        }
        return false;
      });
      if (values.every(e => e === true)) {
        return true;
      }
      if (compare !== "compare") {
        this.validationMessage = errorMessage;
      } else {
        this.validationMessageForCompare = errorMessage;
      }
      return false;
    },
    addClassbreak(classbreaks, i) {
      classbreaks.splice(
        i,
        0,
        classbreaks.length > i
          ? [classbreaks[i][0], classbreaks[i][1]] // create new array!
          : [1, "#ffffff"]
      );
    },
    showColorPicker(id) {
      this.activeColorPicker = this.activeColorPicker === id ? null : id;
    },
    submitClassbreaks() {
      this.$store
        .dispatch("application/saveConfig", {
          morphology_classbreaks: this.morphologyClassbreaks
            .sort((a, b) => (a[0] < b[0] ? -1 : 1))
            .map(cb => (cb[1] === "#ffffff" ? cb[0] : cb.join(":")))
            .join(",")
        })
        .finally(() => this.$store.dispatch("application/loadConfig"));
    },
    submitClassbreaksCompare() {
      this.$store
        .dispatch("application/saveConfig", {
          morphology_classbreaks_compare: this.morphologyClassbreaksCompare
            .sort((a, b) => (a[0] < b[0] ? -1 : 1))
            .map(cb => (cb[1] === "#ffffff" ? cb[0] : cb.join(":")))
            .join(",")
        })
        .finally(() => this.$store.dispatch("application/loadConfig"));
    },
    resetClassbreaks() {
      this.morphologyClassbreaks = this.parseClassbreakString(
        defaults.morphology_classbreaks
      );
    },
    resetClassbreaksCompare() {
      this.morphologyClassbreaksCompare = this.parseClassbreakString(
        defaults.morphology_classbreaks_compare
      );
    },
    parseClassbreakString(str) {
      return str
        .split(",")
        .map(cb => cb.split(":"))
        .map(cb => {
          cb[0] = Number(cb[0]);
          cb[1] = cb[1] || "#ffffff";
          return cb;
        });
    }
  },
  mounted() {
    this.morphologyClassbreaks = this.parseClassbreakString(
      this.config.morphology_classbreaks
    );
    this.morphologyClassbreaksCompare = this.parseClassbreakString(
      this.config.morphology_classbreaks_compare
    );

    this.closeColorPickerListener = e => {
      // Escape
      if (e.keyCode === 27) {
        this.activeColorPicker = null;
      }
    };
    window.addEventListener("keydown", this.closeColorPickerListener);
  },
  destroyed() {
    window.removeEventListener("keydown", this.closeColorPickerListener);
  }
};
</script>