view client/src/components/map/fairway/Profiles.vue @ 1518:cff2cf1f3038

profiles: deleted inline css -.-
author Thomas Junk <thomas.junk@intevation.de>
date Thu, 06 Dec 2018 14:20:32 +0100
parents 9b81ac91a43e
children 276df8dadc14
line wrap: on
line source

<template>
  <div
    :class="[
      'box ui-element rounded bg-white text-nowrap',
      { expanded: showProfiles }
    ]"
  >
    <div>
      <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
        <font-awesome-icon icon="chart-area" class="mr-2"></font-awesome-icon>
        <translate>Profiles</translate>
        <font-awesome-icon
          icon="times"
          class="ml-auto text-muted"
          @click="$store.commit('application/showProfiles', false)"
        ></font-awesome-icon>
      </h6>
      <div
        class="d-flex flex-column p-3 flex-grow-1 text-left position-relative"
      >
        <div
          class="loading d-flex justify-content-center align-items-center"
          v-if="surveysLoading || profileLoading"
        >
          <font-awesome-icon icon="spinner" spin />
        </div>
        <select
          @click="moveToBottleneck"
          v-model="selectedBottleneck"
          class="form-control font-weight-bold"
        >
          <option :value="null">
            <translate>Select Bottleneck</translate>
          </option>
          <option
            v-for="bn in bottlenecks"
            :key="bn.properties.name"
            :value="bn.properties.name"
            >{{ bn.properties.name }}</option
          >
        </select>
        <div v-if="selectedBottleneck">
          <div class="d-flex mt-2">
            <div class="flex-fill">
              <small class="text-muted">
                <translate>Sounding Result</translate>:
              </small>
              <select
                v-model="selectedSurvey"
                class="form-control form-control-sm"
              >
                <option
                  v-for="survey in surveys"
                  :key="survey.date_info"
                  :value="survey"
                  >{{ survey.date_info }}</option
                >
              </select>
            </div>
            <div
              class="flex-fill ml-3"
              v-if="selectedSurvey && surveys.length > 1"
            >
              <small class="text-muted mt-1">
                <translate>Compare with</translate>:
              </small>
              <select
                v-model="additionalSurvey"
                class="form-control form-control-sm"
              >
                <option :value="null">None</option>
                <option
                  v-for="survey in additionalSurveys"
                  :key="survey.date_info"
                  :value="survey"
                  >{{ survey.date_info }}</option
                >
              </select>
            </div>
          </div>
          <hr class="w-100 mb-0" />
          <small class="text-muted d-block mt-2">
            <translate>Saved cross profiles</translate>:
          </small>
          <div class="d-flex">
            <select
              :class="[
                'form-control form-control-sm flex-fill',
                { 'rounded-left-only': selectedCut }
              ]"
              v-model="selectedCut"
            >
              <option></option>
              <option
                v-for="(cut, index) in previousCuts"
                :value="cut"
                :key="index"
                >{{ cut.label }}</option
              >
            </select>
            <button
              class="btn btn-sm btn-danger input-button-right"
              @click="confirmDeleteSelectedCut = true"
              v-if="selectedCut && !confirmDeleteSelectedCut"
            >
              <font-awesome-icon icon="trash" />
            </button>
            <button
              class="btn btn-sm btn-info rounded-0"
              @click="confirmDeleteSelectedCut = false"
              v-if="selectedCut && confirmDeleteSelectedCut"
            >
              <font-awesome-icon icon="times" />
            </button>
            <button
              class="btn btn-sm btn-danger input-button-right"
              @click="deleteSelectedCut"
              v-if="selectedCut && confirmDeleteSelectedCut"
            >
              <font-awesome-icon icon="check" />
            </button>
          </div>
          <small class="text-muted d-block mt-2">
            <translate>Enter coordinates manually</translate>:
          </small>
          <div class="position-relative">
            <input
              class="form-control form-control-sm pr-5"
              placeholder="Lat,Lon,Lat,Lon"
              v-model="coordinatesInput"
            />
            <button
              class="btn btn-sm btn-info position-absolute input-button-right"
              @click="applyManualCoordinates"
              style="top: 0; right: 0;"
              v-if="coordinatesInputIsValid"
            >
              <font-awesome-icon icon="check" />
            </button>
          </div>
          <small class="d-flex text-left mt-2" v-if="startPoint && endPoint">
            <div class="text-nowrap mr-3">
              <b> <translate>Start</translate>: </b> <br />
              Lat: {{ startPoint[1] }} <br />
              Lon: {{ startPoint[0] }}
            </div>
            <div class="text-nowrap">
              <b>End:</b> <br />
              Lat: {{ endPoint[1] }} <br />
              Lon: {{ endPoint[0] }}
            </div>
            <button
              v-clipboard:copy="coordinatesForClipboard"
              v-clipboard:success="onCopyCoordinates"
              class="btn btn-info btn-sm ml-auto mt-auto"
            >
              <font-awesome-icon icon="copy" />
            </button>
          </small>
          <div class="d-flex mt-3">
            <div
              class="pr-3 w-50"
              v-if="startPoint && endPoint && !selectedCut"
            >
              <button
                class="btn btn-info btn-sm w-100"
                @click="showLabelInput = !showLabelInput"
              >
                <font-awesome-icon :icon="showLabelInput ? 'times' : 'check'" />
                {{ showLabelInput ? "Cancel" : "Save" }}
              </button>
            </div>
            <div
              :class="startPoint && endPoint && !selectedCut ? 'w-50' : 'w-100'"
            >
              <button class="btn btn-info btn-sm w-100" @click="toggleCutTool">
                <font-awesome-icon
                  :icon="cutTool && cutTool.getActive() ? 'times' : 'plus'"
                ></font-awesome-icon>
                {{ cutTool && cutTool.getActive() ? "Cancel" : "New" }}
              </button>
            </div>
          </div>
          <div v-if="showLabelInput" class="mt-2">
            <small class="text-muted">
              <translate>Enter label for cross profile</translate>:
            </small>
            <div class="position-relative">
              <input
                class="form-control form-control-sm pr-5"
                v-model="cutLabel"
              />
              <button
                class="btn btn-sm btn-info position-absolute input-button-right"
                @click="saveCut"
                v-if="cutLabel"
                style="top: 0; right: 0;"
              >
                <font-awesome-icon icon="check" />
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<style lang="scss" scoped>
.loading {
  background: rgba(255, 255, 255, 0.9);
  position: absolute;
  z-index: 99;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

.input-button-right {
  border-top-right-radius: $border-radius;
  border-bottom-right-radius: $border-radius;
  border-top-left-radius: 0 !important;
  border-bottom-left-radius: 0 !important;
}

.rounded-left-only {
  border-top-right-radius: 0 !important;
  border-bottom-right-radius: 0 !important;
  border-top-left-radius: $border-radius;
  border-bottom-left-radius: $border-radius;
}
</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.kottlaender@intevation.de>
 */
import { mapState, mapGetters } from "vuex";
import Feature from "ol/Feature";
import LineString from "ol/geom/LineString";
import { displayError, displayInfo } from "../../../lib/errors.js";

export default {
  name: "profiles",
  data() {
    return {
      coordinatesInput: "",
      cutLabel: "",
      showLabelInput: false,
      confirmDeleteSelectedCut: false
    };
  },
  computed: {
    ...mapGetters("map", ["getVSourceByName"]),
    ...mapState("application", ["showProfiles"]),
    ...mapState("map", ["lineTool", "polygonTool", "cutTool"]),
    ...mapState("bottlenecks", ["bottlenecks", "surveys", "surveysLoading"]),
    ...mapState("fairwayprofile", [
      "previousCuts",
      "startPoint",
      "endPoint",
      "profileLoading"
    ]),
    selectedBottleneck: {
      get() {
        return this.$store.state.bottlenecks.selectedBottleneck;
      },
      set(name) {
        this.$store.dispatch("bottlenecks/setSelectedBottleneck", name);
      }
    },
    selectedSurvey: {
      get() {
        return this.$store.state.bottlenecks.selectedSurvey;
      },
      set(survey) {
        this.$store.commit("fairwayprofile/additionalSurvey", null);
        this.$store.commit("bottlenecks/selectedSurvey", survey);
      }
    },
    additionalSurvey: {
      get() {
        return this.$store.state.fairwayprofile.additionalSurvey;
      },
      set(survey) {
        this.$store.commit("fairwayprofile/additionalSurvey", survey);
      }
    },
    selectedCut: {
      get() {
        return this.$store.state.fairwayprofile.selectedCut;
      },
      set(cut) {
        this.$store.commit("fairwayprofile/selectedCut", cut);
        if (!cut) {
          this.$store.commit("fairwayprofile/clearCurrentProfile");
          this.$store.commit("application/showSplitscreen", false);
          this.getVSourceByName("Cut Tool").clear();
        }
      }
    },
    additionalSurveys() {
      return this.surveys.filter(survey => survey !== this.selectedSurvey);
    },
    coordinatesForClipboard() {
      return (
        this.startPoint[1] +
        "," +
        this.startPoint[0] +
        "," +
        this.endPoint[1] +
        "," +
        this.endPoint[0]
      );
    },
    coordinatesInputIsValid() {
      const coordinates = this.coordinatesInput
        .split(",")
        .map(coord => parseFloat(coord.trim()))
        .filter(c => Number(c) === c);
      return coordinates.length === 4;
    }
  },
  watch: {
    selectedBottleneck() {
      this.$store.dispatch("fairwayprofile/previousCuts");
      this.cutLabel =
        this.selectedBottleneck + " (" + new Date().toISOString() + ")";
    },
    selectedSurvey(survey) {
      this.loadProfile(survey);
    },
    additionalSurvey(survey) {
      this.loadProfile(survey);
    },
    selectedCut(cut) {
      if (cut) {
        this.confirmDeleteSelectedCut = false;
        this.applyCoordinates(cut.coordinates);
      }
    }
  },
  methods: {
    loadProfile(survey) {
      if (survey) {
        this.$store.commit("fairwayprofile/profileLoading", true);
        this.$store
          .dispatch("fairwayprofile/loadProfile", survey)
          .finally(() =>
            this.$store.commit("fairwayprofile/profileLoading", false)
          );
      }
    },
    toggleCutTool() {
      this.cutTool.setActive(!this.cutTool.getActive());
      this.lineTool.setActive(false);
      this.polygonTool.setActive(false);
      this.$store.commit("map/setCurrentMeasurement", null);
    },
    onCopyCoordinates() {
      displayInfo({
        title: this.$gettext("Success"),
        message: this.$gettext("Coordinates copied to clipboard!")
      });
    },
    applyManualCoordinates() {
      const coordinates = this.coordinatesInput
        .split(",")
        .map(coord => parseFloat(coord.trim()));
      this.selectedCut = null;
      this.coordinatesInput = "";
      this.applyCoordinates([
        coordinates[1],
        coordinates[0],
        coordinates[3],
        coordinates[2]
      ]);
    },
    applyCoordinates(coordinates) {
      // allow only numbers
      coordinates = coordinates.filter(c => Number(c) === c);
      if (coordinates.length === 4) {
        // draw line on map
        this.getVSourceByName("Cut Tool").clear();
        const cut = new Feature({
          geometry: new LineString([
            [coordinates[0], coordinates[1]],
            [coordinates[2], coordinates[3]]
          ]).transform("EPSG:4326", "EPSG:3857")
        });
        this.getVSourceByName("Cut Tool").addFeature(cut);

        // draw diagram
        this.$store.dispatch("fairwayprofile/cut", cut);
      } else {
        displayError({
          title: this.$gettext("Invalid input"),
          message: this.$gettext(
            "Please enter correct coordinates in the format: Lat,Lon,Lat,Lon"
          )
        });
      }
    },
    saveCut() {
      const previousCuts =
        JSON.parse(localStorage.getItem("previousCuts")) || [];
      const newEntry = {
        label: this.cutLabel,
        bottleneckName: this.selectedBottleneck,
        coordinates: [...this.startPoint, ...this.endPoint],
        timestamp: new Date().getTime()
      };
      const existingEntry = previousCuts.find(cut => {
        return JSON.stringify(cut) === JSON.stringify(newEntry);
      });
      if (!existingEntry) previousCuts.push(newEntry);
      if (previousCuts.length > 100) previousCuts.shift();
      localStorage.setItem("previousCuts", JSON.stringify(previousCuts));
      this.$store.dispatch("fairwayprofile/previousCuts");

      this.showLabelInput = false;
      displayInfo({
        title: this.$gettext("Profile saved!"),
        message: this.$gettext(
          'You can now select these coordinates from the "Saved cross profiles" menu to restore this cross profile.'
        )
      });
    },
    deleteSelectedCut() {
      let previousCuts = JSON.parse(localStorage.getItem("previousCuts")) || [];
      previousCuts = previousCuts.filter(cut => {
        return JSON.stringify(cut) !== JSON.stringify(this.selectedCut);
      });
      localStorage.setItem("previousCuts", JSON.stringify(previousCuts));
      this.$store.commit("fairwayprofile/selectedCut", null);
      this.$store.dispatch("fairwayprofile/previousCuts");
      displayInfo({ title: this.$gettext("Profile deleted!") });
    },
    moveToBottleneck() {
      const bottleneck = this.bottlenecks.find(
        bn => bn.properties.name === this.selectedBottleneck
      );
      if (!bottleneck) return;
      this.$store.commit("map/moveMap", {
        coordinates: bottleneck.geometry.coordinates,
        zoom: 17,
        preventZoomOut: true
      });
    }
  }
};
</script>