diff client/src/components/Search.vue @ 1558:0ded4c56978e

refac: component filestructure. remove admin/map hierarchy
author Thomas Junk <thomas.junk@intevation.de>
date Wed, 12 Dec 2018 09:22:20 +0100
parents client/src/components/map/Search.vue@9b81ac91a43e
children f2d24dceecc7
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/Search.vue	Wed Dec 12 09:22:20 2018 +0100
@@ -0,0 +1,292 @@
+<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"></font-awesome-icon>
+      </span>
+    </div>
+    <div
+      :class="[
+        'searchgroup',
+        {
+          'searchgroup-collapsed': !showSearchbar,
+          big:
+            showContextBox &&
+            ['bottlenecks', 'staging'].indexOf(contextBoxContent) !== -1
+        }
+      ]"
+    >
+      <input
+        @keyup.enter="takeFirstSearchresult"
+        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 of searchResults"
+        :key="entry.name"
+        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
+          />
+          {{ entry.name }}
+        </a>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.searchcontainer {
+  opacity: 0.96;
+}
+
+.searchcontainer .searchbar {
+  border-top-left-radius: 0 !important;
+  border-bottom-left-radius: 0 !important;
+}
+
+.searchgroup {
+  margin-left: -3px;
+  transition: width 0.3s;
+  width: 300px;
+  overflow: hidden;
+}
+
+.searchgroup.big {
+  width: 571px;
+}
+
+.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;
+}
+</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 "lodash.debounce";
+import { mapState } from "vuex";
+
+import { displayError } from "../lib/errors.js";
+import { HTTP } from "../lib/http";
+
+const setFocus = () => document.querySelector("#search").focus();
+
+export default {
+  name: "search",
+  data() {
+    return {
+      searchQueryIsDirty: false,
+      searchResults: null,
+      isSearching: false
+    };
+  },
+  computed: {
+    ...mapState("application", [
+      "showSearchbar",
+      "showContextBox",
+      "contextBoxContent"
+    ]),
+    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",
+        {
+          "d-flex": this.contextBoxContent !== "imports",
+          "d-none": this.contextBoxContent === "imports" && this.showContextBox
+        }
+      ];
+    },
+    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;
+      this.triggerSearch();
+    }
+  },
+  methods: {
+    takeFirstSearchresult() {
+      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",
+        { string: this.searchQuery },
+        {
+          headers: {
+            "X-Gemma-Auth": localStorage.getItem("token"),
+            "Content-type": "text/xml; charset=UTF-8"
+          }
+        }
+      )
+        .then(response => {
+          // console.log("got:", response.data);
+          this.searchResults = response.data;
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+
+      this.isCalculating = false;
+      this.searchQueryIsDirty = false;
+    },
+    moveToSearchResult(resultEntry) {
+      // DEBUG console.log("Moving to", resultEntry);
+      if (resultEntry.geom.type == "Point") {
+        let zoom = 11;
+        if (resultEntry.type === "bottleneck") zoom = 17;
+        if (resultEntry.type === "rhm") zoom = 15;
+        if (resultEntry.type === "city") zoom = 13;
+
+        this.$store.commit("map/moveMap", {
+          coordinates: resultEntry.geom.coordinates,
+          zoom,
+          preventZoomOut: true
+        });
+      }
+      // 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>