view client/src/application/Topbar.vue @ 1146:74e180ad3d6b

fairway profile UI improvements splitscreen button position at top of profile container bottleneck name and survey date as headline in profile container moved logout button to sidebar menu to avoid unnecessary overlapping
author Markus Kottlaender <markus@intevation.de>
date Tue, 13 Nov 2018 11:12:12 +0100
parents 846e336d8ee5
children da75faa8043f
line wrap: on
line source

<template>
    <div class="topbar d-flex flex-row">
        <div @click="$store.commit('application/showSidebar', !showSidebar)">
            <i class="ui-element menubutton d-print-none fa fa-bars"></i>
        </div>
        <div v-if="routeName == 'mainview'" :class="searchbarContainerStyle">
            <div class="input-group-prepend shadow">
                <span @click="toggleSearchbar" class="ui-element input-group-text searchlabel d-print-none" for="search">
                    <i class="fa fa-search d-print-none"></i>
                </span>
            </div>
            <div class="searchgroup">
                <input @keyup.enter="takeFirstSearchresult" v-if="showSearchbar" id="search" v-model="searchQuery" type="text" class="form-control ui-element search searchbar d-print-none">
                <ul v-if="showSearchbar && searchResults !== null " class="list-group d-print-none">
                    <li v-for="entry of searchResults" :key="entry.name" class="list-group-item">
                        <a href="#" @click.prevent="moveToSearchResult(entry)">{{entry.name}}</a>
                    </li>
                </ul>
            </div>
        </div>
        <div class="">
            <Layers v-if="routeName == 'mainview'"></Layers>
        </div>
        <div class="">
            <Identify v-if="routeName == 'mainview'"></Identify>
        </div>
    </div>
</template>

<style lang="scss" scoped>
.searchgroup {
  width: 90%;
}
.splitbutton {
  height: $icon-height;
}
.list-group {
  pointer-events: auto;
}
.splitscreen {
  background-color: white;
  padding: $small-offset;
  margin-right: $small-offset;
  margin-left: $offset;
  border-radius: $border-radius;
  height: $icon-height;
  width: $icon-width;
}

.menubutton {
  background-color: white;
  padding: $small-offset;
  border-radius: $border-radius;
  margin-left: $offset;
  height: $icon-height;
  width: $icon-width;
}

.searchcontainer {
  height: $icon-height;
  border-radius: 0.25rem;
}

.searchbar-expanded {
  margin-left: 22vw;
  margin-right: auto;
  width: $searchbar-width !important;
}

.searchbar-collapsed {
  margin-left: auto;
  margin-right: $small-offset;
  width: $icon-width !important;
  transition: $transition-fast;
}

.searchbar {
  margin-left: auto;
  margin-right: auto;
  height: $icon-height !important;
}

.searchlabel {
  background-color: white !important;
}

.topbar {
  padding-top: $offset;
  margin-right: $offset;
}

.logout {
  font-size: x-large;
}
</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):
 * Thomas Junk <thomas.junk@intevation.de>
 */
import debounce from "lodash.debounce";
import { fromLonLat } from "ol/proj";
import { mapState } from "vuex";

import { displayError } from "../application/lib/errors.js";
import { HTTP } from "../application/lib/http";
import Identify from "../identify/Identify";
import Layers from "../layers/Layers";

const setFocus = () => document.querySelector("#search").focus();

export default {
  name: "topbar",
  components: {
    Identify: Identify,
    Layers: Layers
  },
  data() {
    return {
      searchQuery: "",
      searchQueryIsDirty: false,
      searchResults: null,
      isSearching: false
    };
  },
  computed: {
    ...mapState("application", [
      "showSidebar",
      "showSplitscreen",
      "showSearchbar"
    ]),
    ...mapState("map", ["openLayersMap"]),
    ...mapState("fairwayprofile", ["currentProfile"]),
    searchIndicator: function() {
      if (this.isSearching) {
        return "⟳";
      } else if (this.searchQueryIsDirty) {
        return "";
      } else {
        return "✓";
      }
    },
    searchbarContainerStyle() {
      return {
        "input-group": true,
        searchcontainer: true,
        "searchbar-collapsed": !this.showSearchbar,
        "searchbar-expanded": this.showSearchbar
      };
    }
  },
  props: ["routeName"],
  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: "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 view = this.openLayersMap.getView();
        const currentZoom = view.getZoom();
        let newZoom = 11;
        if (resultEntry.type === "bottleneck")
          newZoom = Math.max(17, currentZoom);
        if (resultEntry.type === "rhm") newZoom = Math.max(15, currentZoom);
        if (resultEntry.type === "city") newZoom = Math.max(13, currentZoom);
        view.animate(
          {
            zoom: newZoom,
            center: fromLonLat(
              resultEntry.geom.coordinates,
              view.getProjection()
            )
          },
          700
        );
      }
      // this.searchQuery = ""; // clear search query again
      this.toggleSearchbar();
    },
    toggleSearchbar() {
      if (!this.showSearchbar) {
        setTimeout(setFocus, 300);
      }
      this.$store.commit("application/showSearchbar", !this.showSearchbar);
    }
  }
};
</script>