Mercurial > gemma
view client/src/components/fairway/Profiles.vue @ 2282:2e40909a975d
clean searchbar when context is changed
author | Thomas Junk <thomas.junk@intevation.de> |
---|---|
date | Fri, 15 Feb 2019 11:55:56 +0100 |
parents | 531e776cb81d |
children | 242c170e00ce |
line wrap: on
line source
<template> <div :class="[ 'box ui-element rounded bg-white text-nowrap', { expanded: showProfiles } ]" > <div style="width: 18rem"> <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 pointer" @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" >{{ 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"; 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) .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("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: { 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("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/moveToExtent", { feature: bottleneck, zoom: 17, preventZoomOut: true }); } } }; </script>