Mercurial > gemma
view client/src/components/Search.vue @ 5167:a5fd84c4f2fe
timeslider: avoid moving cursor to future area (click event)
* move the cursor to "now" instead of 12:00 in case "now" is earlier
when clicking on value of "today" on time slider and
zoom level is for days.
author | Fadi Abbud <fadi.abbud@intevation.de> |
---|---|
date | Tue, 07 Apr 2020 15:16:11 +0200 |
parents | 8d5e3ce27d20 |
children | 19715261750e |
line wrap: on
line source
<template> <div :class="searchbarContainerStyle"> <div class="input-group-prepend m-0 d-print-none"> <span @click="toggleSearchbar" :class="searchButtonStyle" for="search"> <font-awesome-icon icon="search" /> </span> </div> <div :class="[ 'searchgroup', { 'searchgroup-collapsed': !showSearchbar, big: showContextBox && ['bottlenecks', 'staging', 'stretches'].indexOf( contextBoxContent ) !== -1 } ]" > <input @keyup.enter="triggerEnter" id="search" v-model="searchQuery" type="text" :class="searchInputStyle" /> </div> <div v-if="showSearchbar && searchResults !== null && !showContextBox" class="searchresults border-top ui-element bg-white rounded-bottom d-print-none position-absolute" > <div v-for="(entry, index) of searchResults" :key="index" class="border-top text-left" > <a href="#" @click.prevent="moveToSearchResult(entry)" class="p-2 d-block text-nowrap" > <font-awesome-icon icon="ship" v-if="entry.type === 'bottleneck'" class="mr-1" fixed-width /> <font-awesome-icon icon="water" v-if="entry.type === 'rhm'" class="mr-1" fixed-width /> <font-awesome-icon icon="city" v-if="entry.type === 'city'" class="mr-1" fixed-width /> <font-awesome-icon icon="ruler-vertical" v-if="entry.type === 'gauge'" class="mr-1" fixed-width /> <font-awesome-icon icon="road" v-if="['stretch', 'section'].includes(entry.type)" class="mr-1" fixed-width /> {{ entry.name }} <span v-if="entry.location || entry.locationcode" >({{ entry.location }}<span v-if="entry.location && entry.locationcode">, </span >{{ entry.locationcode }})</span > </a> </div> </div> </div> </template> <style lang="scss" scoped> .searchcontainer { opacity: 0.96; width: 860px; } .searchcontainer .searchbar { border-top-left-radius: 0 !important; border-bottom-left-radius: 0 !important; } .searchgroup { width: 827px; overflow: hidden; } .searchgroup-collapsed { width: 0; } .searchbar { height: 2rem !important; box-shadow: none !important; } .searchbar.rounded-top-right { border-radius: 0 !important; border-top-right-radius: 0.25rem !important; } .searchlabel.rounded-top-left { border-radius: 0 !important; border-top-left-radius: 0.25rem !important; } .input-group-text { height: 2rem; width: 2rem; } .input-group-prepend svg path { fill: #666; } .searchresults { box-shadow: 0 0.1rem 0.5rem rgba(0, 0, 0, 0.2); top: 2rem; left: 0; right: 0; max-height: 24rem; overflow: auto; } .searchresults > div:first-child { border-top: 0 !important; } .searchresults a { text-decoration: none; } .searchresults a:hover { background: #f8f8f8; } .smallbox { width: 2rem; } </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 debounce from "debounce"; import { mapState, mapGetters } from "vuex"; import { displayError } from "@/lib/errors"; import { HTTP } from "@/lib/http"; import { format } from "date-fns"; const setFocus = () => document.querySelector("#search").focus(); export default { name: "search", data() { return { searchQueryIsDirty: false, searchResults: null, isSearching: false }; }, computed: { ...mapState("application", [ "showSearchbar", "showContextBox", "contextBoxContent", "showTimeSlider", "currentVisibleTime" ]), ...mapState("imports", ["startDate", "endDate"]), ...mapGetters("imports", ["filters"]), ...mapGetters("map", ["openLayersMap"]), searchQuery: { get() { return this.$store.state.application.searchQuery; }, set(value) { this.$store.commit("application/searchQuery", value); } }, searchIndicator: function() { if (this.isSearching) { return "⟳"; } else if (this.searchQueryIsDirty) { return ""; } else { return "✓"; } }, searchbarContainerStyle() { return [ "input-group searchcontainer shadow-xs rounded", { "d-flex": this.contextBoxContent !== "imports", "d-none": this.contextBoxContent === "imports" && this.showContextBox, smallbox: !this.showSearchbar } ]; }, searchInputStyle() { return [ "form-control ui-element search searchbar d-print-none border-0", { "rounded-top-right": this.showContextBox || this.searchResults } ]; }, searchButtonStyle() { return [ "ui-element input-group-text p-0 d-flex border-0 justify-content-center searchlabel bg-white d-print-none", { rounded: !this.showSearchbar, "rounded-left": this.showSearchbar, "rounded-top-left": this.showSearchbar && (this.showContextBox || this.searchResults) } ]; } }, watch: { searchQuery: function() { this.searchQueryIsDirty = true; if (!this.showContextBox) this.triggerSearch(); }, currentVisibleTime() { this.doSearch(); } }, methods: { loadLogs() { this.$store .dispatch("imports/getImports", { filter: this.filters, from: format(this.startDate, "YYYY-MM-DDTHH:mm:ss.SSS"), to: format(this.endDate, "YYYY-MM-DDTHH:mm:ss.SSS"), query: this.searchQuery }) .then(() => {}) .catch(error => { let message = "Backend not reachable"; if (error.response) { const { status, data } = error.response; message = `${status}: ${data.message || data}`; } displayError({ title: this.$gettext("Backend Error"), message: message }); }); }, triggerEnter() { if (this.showContextBox && this.contextBoxContent === "importoverview") { this.loadLogs(); } if (!this.searchResults || this.searchResults.length != 1) return; this.moveToSearchResult(this.searchResults[0]); }, triggerSearch: debounce(function() { this.doSearch(); }, 500), doSearch() { this.isCalculating = true; this.searchResults = null; if (this.searchQuery == "") { return; } HTTP.post( "/search", this.showTimeSlider ? { string: this.searchQuery, time: this.currentVisibleTime.toISOString() } : { string: this.searchQuery }, { headers: { "X-Gemma-Auth": localStorage.getItem("token"), "Content-type": "text/xml; charset=UTF-8" } } ) .then(response => { this.searchResults = response.data; }) .catch(error => { let message = "Backend not reachable"; if (error.response) { const { status, data } = error.response; message = `${status}: ${data.message || data}`; } displayError({ title: this.$gettext("Backend Error"), message: message }); }); this.isCalculating = false; this.searchQueryIsDirty = false; }, moveToSearchResult(resultEntry) { let zoom = 16; if (resultEntry.type === "bottleneck") { this.openLayersMap() .getLayer("BOTTLENECKS") .setVisible(true); } if (resultEntry.type === "rhm") { this.openLayersMap() .getLayer("DISTANCEMARKSAXIS") .setVisible(true); } if (resultEntry.type === "gauge") { this.openLayersMap() .getLayer("GAUGES") .setVisible(true); } if (resultEntry.type === "stretch") { this.$store.commit( "imports/selectedStretchId", "stretches_geoserver." + resultEntry.id ); this.openLayersMap() .getLayer("STRETCHES") .setVisible(true); } if (resultEntry.type === "section") { this.$store.commit( "imports/selectedSectionId", "sections_geoserver." + resultEntry.id ); this.openLayersMap() .getLayer("SECTIONS") .setVisible(true); } if (resultEntry.type === "city") zoom = 13; if (resultEntry.geom.type == "Point") { this.$store.dispatch("map/moveMap", { coordinates: resultEntry.geom.coordinates, zoom, preventZoomOut: true }); } else if (resultEntry.geom.type == "Polygon") { const boundingBox = [ Math.min(...resultEntry.geom.coordinates[0].map(c => c[0])), Math.min(...resultEntry.geom.coordinates[0].map(c => c[1])), Math.max(...resultEntry.geom.coordinates[0].map(c => c[0])), Math.max(...resultEntry.geom.coordinates[0].map(c => c[1])) ]; this.$store.dispatch("map/moveToBoundingBox", { boundingBox, zoom }); } // this.searchQuery = ""; // clear search query again this.toggleSearchbar(); }, toggleSearchbar() { if (!this.showContextBox) { if (!this.showSearchbar) { setTimeout(setFocus, 300); } this.$store.commit("application/showSearchbar", !this.showSearchbar); } } } }; </script>