Mercurial > gemma
view client/src/components/fairway/Profiles.vue @ 2503:51dbcbf11c5f critical-bottlenecks
client: addendum for e13daf439068
Of course, as you'd expect, this only solves the problem if you don't care about
significant changes in the tables sorting behavior. To point out the difference
this commit shows the other way to solve the problem without changing the tables
behavior.
author | Markus Kottlaender <markus@intevation.de> |
---|---|
date | Mon, 04 Mar 2019 16:28:49 +0100 |
parents | 64ff5984351e |
children | bb5286acfee2 |
line wrap: on
line source
<template> <div :class="[ 'box ui-element rounded bg-white text-nowrap', { expanded: showProfiles } ]" > <div style="width: 18rem"> <UIBoxHeader icon="chart-area" title="Profiles" :closeCallback="close" /> <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 bottlenecksList" :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" >{{ formatSurveyDate(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" >{{ formatSurveyDate(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" class="pointer" /> </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"; import { formatSurveyDate } from "@/lib/date.js"; import { LAYERS } from "@/store/map.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", [ "bottlenecksList", "surveys", "surveysLoading" ]), ...mapState("fairwayprofile", [ "previousCuts", "startPoint", "endPoint", "profileLoading" ]), selectedBottleneck: { get() { return this.$store.state.bottlenecks.selectedBottleneck; }, set(name) { this.$store .dispatch("bottlenecks/setSelectedBottleneck", name) .then(() => { this.$store.commit("bottlenecks/setFirstSurveySelected"); }); } }, 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(LAYERS.CUTTOOL).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: { close() { this.$store.commit("application/showProfiles", false); }, formatSurveyDate(date) { return formatSurveyDate(date); }, 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(LAYERS.CUTTOOL).clear(); const cut = new Feature({ geometry: new LineString([ [coordinates[0], coordinates[1]], [coordinates[2], coordinates[3]] ]).transform("EPSG:4326", "EPSG:3857") }); this.getVSourceByName(LAYERS.CUTTOOL).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.bottlenecksList.find( bn => bn.properties.name === this.selectedBottleneck ); if (!bottleneck) return; this.$store.commit("map/moveToExtent", { feature: bottleneck, zoom: 17, preventZoomOut: true }); } }, mounted() { this.$store.dispatch("bottlenecks/loadBottlenecksList"); } }; </script>