changeset 1140:2e06bc53b002

separating line/polygon/cut tools in UI Measurements can now be made while a bottleneck and sounding data is selected. The open layers interaction object(s) are now in the vuex store to disable them from other components (Morphtool.vue). Line and Polygon are now to separate buttons.
author Markus Kottlaender <markus@intevation.de>
date Mon, 12 Nov 2018 14:45:07 +0100
parents 2fda33d55d81
children a473d91b0856
files client/src/App.vue client/src/drawtool/Drawtool.vue client/src/linetool/Linetool.vue client/src/map/Maplayer.vue client/src/morphtool/Morphtool.vue client/src/pdftool/Pdftool.vue client/src/store/map.js client/src/usermanagement/Usermanagement.vue
diffstat 8 files changed, 305 insertions(+), 232 deletions(-) [+]
line wrap: on
line diff
--- a/client/src/App.vue	Wed Nov 07 15:26:46 2018 +0100
+++ b/client/src/App.vue	Mon Nov 12 14:45:07 2018 +0100
@@ -15,8 +15,8 @@
             <div class="bottomcontainer d-flex flex-row align-items-end">
                 <Userbar></Userbar>
                 <Morphtool v-if="routeName == 'mainview'"></Morphtool>
-                <Linetool v-if="routeName == 'mainview'"></Linetool>
                 <Pdftool v-if="routeName == 'mainview'"></Pdftool>
+                <Drawtool v-if="routeName == 'mainview'"></Drawtool>
             </div>
             <Zoom v-if="routeName == 'mainview'"></Zoom>
         </div>
@@ -115,7 +115,7 @@
     Bottlenecks: () => import("./bottlenecks/Bottlenecks"),
     Topbar: () => import("./application/Topbar"),
     Userbar: () => import("./application/Userbar"),
-    Linetool: () => import("./linetool/Linetool"),
+    Drawtool: () => import("./drawtool/Drawtool"),
     Morphtool: () => import("./morphtool/Morphtool"),
     Pdftool: () => import("./pdftool/Pdftool"),
     Zoom: () => import("./zoom/zoom")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/drawtool/Drawtool.vue	Mon Nov 12 14:45:07 2018 +0100
@@ -0,0 +1,226 @@
+<template>
+    <div class="d-flex flex-column">
+        <div @click="toggleLineMode" class="ui-element d-flex shadow drawtool">
+            <i :class="['fa fa-pencil', {inverted: drawMode === 'LineString'}]"></i>
+        </div>
+        <div @click="togglePolygonMode" class="ui-element d-flex shadow drawtool">
+            <i :class="['fa fa-edit', {inverted: drawMode === 'Polygon'}]"></i>
+        </div>
+        <div @click="toggleCutMode" class="ui-element d-flex shadow drawtool" v-if="selectedSurvey">
+            <i :class="['fa fa-area-chart', {inverted: cutMode}]"></i>
+        </div>
+    </div>
+</template>
+
+<style lang="sass" scoped>
+.drawtool
+  background-color: white
+  padding: $small-offset
+  border-radius: $border-radius
+  margin-left: $offset
+  height: $icon-width
+  width: $icon-height
+  margin-bottom: $offset
+  margin-right: $offset
+  z-index: 2
+
+.inverted
+  color: #0077ff
+</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>
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapState, mapGetters } from "vuex";
+import { getLength, getArea } from "ol/sphere.js";
+import LineString from "ol/geom/LineString.js";
+import Draw from "ol/interaction/Draw.js";
+import { displayError } from "../application/lib/errors.js";
+import { calculateFairwayCoordinates } from "../application/lib/geo.js";
+
+const DEMODATA = 2.5;
+
+export default {
+  name: "drawtool",
+  computed: {
+    ...mapGetters("map", ["getLayerByName"]),
+    ...mapState("map", ["drawMode", "drawTool", "cutMode", "cutTool", "openLayersMap"]),
+    ...mapState("bottlenecks", ["selectedSurvey"])
+  },
+  methods: {
+    toggleLineMode() {
+      this.disableDrawTool();
+      this.disableCutTool();
+      this.$store.commit("map/drawMode", this.drawMode !== "LineString" ? "LineString" : null);
+      this.$store.commit("map/cutMode", null);
+      if (this.drawMode) this.enableDrawTool();
+    },
+    togglePolygonMode() {
+      this.disableDrawTool();
+      this.disableCutTool();
+      this.$store.commit("map/drawMode", this.drawMode !== "Polygon" ? "Polygon" : null);
+      this.$store.commit("map/cutMode", null);
+      if (this.drawMode) this.enableDrawTool();
+    },
+    toggleCutMode() {
+      this.disableCutTool();
+      this.disableDrawTool();
+      this.$store.commit('map/cutMode', !this.cutMode);
+      this.$store.commit("map/drawMode", null);
+      if (this.cutMode) this.enableCutTool();
+    },
+    enableDrawTool() {
+      const drawVectorSrc = this.getLayerByName("Draw Tool").data.getSource();
+      drawVectorSrc.clear();
+      const drawTool = new Draw({
+        source: drawVectorSrc,
+        type: this.drawMode,
+        maxPoints: this.drawMode === "LineString" ? 2 : 50
+      });
+      drawTool.on("drawstart", () => {
+        drawVectorSrc.clear();
+        this.$store.commit("map/setCurrentMeasurement", null);
+        // we are not setting an id here, to avoid the regular identify to
+        // pick it up
+        // event.feature.setId("drawn.1"); // unique id for new feature
+      });
+      drawTool.on("drawend", this.drawEnd);
+      this.$store.commit("map/drawTool", drawTool);
+      this.openLayersMap.addInteraction(drawTool);
+    },
+    disableDrawTool() {
+      this.$store.commit("map/setCurrentMeasurement", null);
+      this.getLayerByName("Draw Tool").data.getSource().clear();
+      this.openLayersMap.removeInteraction(this.drawTool);
+      this.$store.commit("map/drawTool", null);
+    },
+    drawEnd(event) {
+      if (this.drawMode === "Polygon") {
+        const areaSize = getArea(event.feature.getGeometry());
+        // also place the a rounded areaSize in a property,
+        // so identify will show it
+        this.$store.commit("map/setCurrentMeasurement", {
+          quantity: "Area",
+          unitSymbol: areaSize > 100000 ? "km²" : "m²",
+          value: areaSize > 100000
+            ? Math.round(areaSize / 1000) / 1000 // convert into 1 km² == 1000*1000 m² and round to 1000 m²
+            : Math.round(areaSize)
+        });
+      }
+      if (this.drawMode === "LineString") {
+        const length = getLength(event.feature.getGeometry());
+        this.$store.commit("map/setCurrentMeasurement", {
+          quantity: "Length",
+          unitSymbol: "m",
+          value: Math.round(length * 10) / 10
+        });
+      }
+    },
+    enableCutTool() {
+      const cutVectorSrc = this.getLayerByName("Cut Tool").data.getSource();
+      cutVectorSrc.clear();
+      const cutTool = new Draw({
+        source: cutVectorSrc,
+        type: "LineString",
+        maxPoints: 2
+      });
+      cutTool.on("drawstart", () => {
+        cutVectorSrc.clear();
+        // we are not setting an id here, to avoid the regular identify to
+        // pick it up
+        // event.feature.setId("drawn.1"); // unique id for new feature
+      });
+      cutTool.on("drawend", this.cutEnd);
+      this.$store.commit("map/cutTool", cutTool);
+      this.openLayersMap.addInteraction(cutTool);
+    },
+    disableCutTool() {
+      this.$store.commit("map/setCurrentMeasurement", null);
+      this.getLayerByName("Cut Tool").data.getSource().clear();
+      this.openLayersMap.removeInteraction(this.cutTool);
+      this.$store.commit("map/cutTool", null);
+    },
+    cutEnd(event) {
+      const length = getLength(event.feature.getGeometry());
+      this.$store.commit("map/setCurrentMeasurement", {
+        quantity: "Length",
+        unitSymbol: "m",
+        value: Math.round(length * 10) / 10
+      });
+
+      // if a survey has been selected, request a profile
+      // TODO an improvement could be to check if the line intersects
+      // with the bottleneck area's polygon before trying the server request
+      if (this.selectedSurvey) {
+        this.$store.commit("fairwayprofile/clearCurrentProfile");
+        console.log("requesting profile for", this.selectedSurvey);
+        const inputLineString = event.feature.getGeometry().clone();
+        inputLineString.transform("EPSG:3857", "EPSG:4326");
+        const [start, end] = inputLineString
+          .getCoordinates()
+          .map(coords => coords.map(coord => parseFloat(coord.toFixed(8))));
+        this.$store.commit("fairwayprofile/setStartPoint", start);
+        this.$store.commit("fairwayprofile/setEndPoint", end);
+        const profileLine = new LineString([start, end]);
+        this.$store
+          .dispatch("fairwayprofile/loadProfile", this.selectedSurvey)
+          .then(() => {
+            var vectorSource = this.getLayerByName(
+              "Fairway Dimensions"
+            ).data.getSource();
+            this.calculateIntersection(vectorSource, profileLine);
+          })
+          .then(() => {
+            this.$store.commit("application/showSplitscreen", true);
+          })
+          .catch(error => {
+            const { status, data } = error.response;
+            displayError({
+              title: "Backend Error",
+              message: `${status}: ${data.message || data}`
+            });
+          });
+      }
+    },
+    calculateIntersection(vectorSource, profileLine) {
+      const transformedLine = profileLine
+        .clone()
+        .transform("EPSG:4326", "EPSG:3857")
+        .getExtent();
+      const featureCallback = feature => {
+        // transform back to prepare for usage
+        var intersectingPolygon = feature
+          .getGeometry()
+          .clone()
+          .transform("EPSG:3857", "EPSG:4326");
+        const fairwayCoordinates = calculateFairwayCoordinates(
+          profileLine,
+          intersectingPolygon,
+          DEMODATA
+        );
+        this.$store.commit(
+          "fairwayprofile/setFairwayCoordinates",
+          fairwayCoordinates
+        );
+      };
+      vectorSource.forEachFeatureIntersectingExtent(
+        // need to use EPSG:3857 which is the proj of vectorSource
+        transformedLine,
+        featureCallback
+      );
+    },
+  }
+};
+</script>
--- a/client/src/linetool/Linetool.vue	Wed Nov 07 15:26:46 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-<template>
-    <div @click="cycleDrawMode" class="ui-element d-flex shadow drawtool">
-        <i :class="icon"></i>
-    </div>
-</template>
-
-<style lang="scss" scoped>
-.drawtool {
-  position: absolute;
-  bottom: 0;
-  right: 0;
-  background-color: white;
-  padding: $small-offset;
-  border-radius: $border-radius;
-  margin-left: $offset;
-  height: $icon-width;
-  width: $icon-height;
-  margin-bottom: $offset;
-  margin-right: $offset;
-  z-index: 2;
-}
-
-.inverted {
-  color: #0077ff;
-}
-</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 { mapState } from "vuex";
-
-export default {
-  name: "linetool",
-  methods: {
-    cycleDrawMode() {
-      if (!this.selectedSurvey && this.drawMode === "LineString") {
-        this.$store.commit("map/activateDrawModePolygon");
-      } else {
-        this.$store.commit("map/toggleDrawModeLine");
-      }
-    }
-  },
-  computed: {
-    ...mapState("map", ["identifiedFeatures", "drawMode"]),
-    ...mapState("bottlenecks", ["selectedSurvey"]),
-    icon() {
-      return {
-        fa: true,
-        "fa-area-chart": this.selectedSurvey,
-        "fa-edit": !this.selectedSurvey && this.drawMode === "Polygon",
-        "fa-pencil": !this.selectedSurvey && this.drawMode !== "Polygon",
-        inverted: this.drawMode
-      };
-    }
-  }
-};
-</script>
--- a/client/src/map/Maplayer.vue	Wed Nov 07 15:26:46 2018 +0100
+++ b/client/src/map/Maplayer.vue	Mon Nov 12 14:45:07 2018 +0100
@@ -44,18 +44,8 @@
 import "ol/ol.css";
 import { Map, View } from "ol";
 import { WFS, GeoJSON } from "ol/format.js";
-import LineString from "ol/geom/LineString.js";
-import Draw from "ol/interaction/Draw.js";
-import { Vector as VectorLayer } from "ol/layer.js";
-import { Vector as VectorSource } from "ol/source.js";
-import { getLength, getArea } from "ol/sphere.js";
 import { Stroke, Style, Fill } from "ol/style.js";
 
-import { displayError } from "../application/lib/errors.js";
-import { calculateFairwayCoordinates } from "../application/lib/geo.js";
-
-const DEMODATA = 2.5;
-
 /* for the sake of debugging */
 /* eslint-disable no-console */
 export default {
@@ -63,8 +53,7 @@
   props: ["lat", "long", "zoom", "split"],
   data() {
     return {
-      projection: "EPSG:3857",
-      interaction: null
+      projection: "EPSG:3857"
     };
   },
   computed: {
@@ -79,125 +68,6 @@
     }
   },
   methods: {
-    removeCurrentInteraction() {
-      this.$store.commit("map/setCurrentMeasurement", null);
-      this.getLayerByName("Draw Tool").data.getSource().clear();
-      this.openLayersMap.removeInteraction(this.interaction);
-      this.interaction = null;
-    },
-    createInteraction(drawMode) {
-      const drawVectorSrc = this.getLayerByName("Draw Tool").data.getSource();
-      drawVectorSrc.clear();
-      var draw = new Draw({
-        source: drawVectorSrc,
-        type: drawMode,
-        maxPoints: drawMode === "LineString" ? 2 : 50
-      });
-      draw.on("drawstart", () => {
-        drawVectorSrc.clear();
-        this.$store.commit("map/setCurrentMeasurement", null);
-        // we are not setting an id here, to avoid the regular identify to
-        // pick it up
-        // event.feature.setId("drawn.1"); // unique id for new feature
-      });
-      draw.on("drawend", this.drawEnd);
-      return draw;
-    },
-    drawEnd(event) {
-      if (this.drawMode === "Polygon") {
-        const areaSize = getArea(event.feature.getGeometry());
-        // also place the a rounded areaSize in a property,
-        // so identify will show it
-        if (areaSize > 100000) {
-          this.$store.commit("map/setCurrentMeasurement", {
-            quantity: "Area",
-            unitSymbol: "km²",
-            // convert into 1 km² == 1000*1000 m² and round to 1000 m²
-            value: Math.round(areaSize / 1000) / 1000
-          });
-        } else {
-          this.$store.commit("map/setCurrentMeasurement", {
-            quantity: "Area",
-            unitSymbol: "m²",
-            value: Math.round(areaSize)
-          });
-        }
-      }
-      if (this.drawMode === "LineString") {
-        const length = getLength(event.feature.getGeometry());
-        this.$store.commit("map/setCurrentMeasurement", {
-          quantity: "Length",
-          unitSymbol: "m",
-          value: Math.round(length * 10) / 10
-        });
-      }
-
-      // if a survey has been selected, request a profile
-      // TODO an improvement could be to check if the line intersects
-      // with the bottleneck area's polygon before trying the server request
-      if (this.selectedSurvey) {
-        this.$store.commit("fairwayprofile/clearCurrentProfile");
-        console.log("requesting profile for", this.selectedSurvey);
-        const inputLineString = event.feature.getGeometry().clone();
-        inputLineString.transform("EPSG:3857", "EPSG:4326");
-        const [start, end] = inputLineString
-          .getCoordinates()
-          .map(coords => coords.map(coord => parseFloat(coord.toFixed(8))));
-        this.$store.commit("fairwayprofile/setStartPoint", start);
-        this.$store.commit("fairwayprofile/setEndPoint", end);
-        const profileLine = new LineString([start, end]);
-        this.$store
-          .dispatch("fairwayprofile/loadProfile", this.selectedSurvey)
-          .then(() => {
-            var vectorSource = this.getLayerByName(
-              "Fairway Dimensions"
-            ).data.getSource();
-            this.calculateIntersection(vectorSource, profileLine);
-          })
-          .then(() => {
-            this.$store.commit("application/showSplitscreen", true);
-          })
-          .catch(error => {
-            const { status, data } = error.response;
-            displayError({
-              title: "Backend Error",
-              message: `${status}: ${data.message || data}`
-            });
-          });
-      }
-    },
-    calculateIntersection(vectorSource, profileLine) {
-      const transformedLine = profileLine
-        .clone()
-        .transform("EPSG:4326", "EPSG:3857")
-        .getExtent();
-      const featureCallback = feature => {
-        // transform back to prepare for usage
-        var intersectingPolygon = feature
-          .getGeometry()
-          .clone()
-          .transform("EPSG:3857", "EPSG:4326");
-        const fairwayCoordinates = calculateFairwayCoordinates(
-          profileLine,
-          intersectingPolygon,
-          DEMODATA
-        );
-        this.$store.commit(
-          "fairwayprofile/setFairwayCoordinates",
-          fairwayCoordinates
-        );
-      };
-      vectorSource.forEachFeatureIntersectingExtent(
-        // need to use EPSG:3857 which is the proj of vectorSource
-        transformedLine,
-        featureCallback
-      );
-    },
-    activateInteraction() {
-      const interaction = this.createInteraction(this.drawMode);
-      this.interaction = interaction;
-      this.openLayersMap.addInteraction(interaction);
-    },
     identify(coordinate, pixel) {
       this.$store.commit("map/setIdentifiedFeatures", []);
       // checking our WFS layers
@@ -338,14 +208,6 @@
     }
   },
   watch: {
-    drawMode(newValue) {
-      if (this.interaction) {
-        this.removeCurrentInteraction();
-      }
-      if (newValue) {
-        this.activateInteraction();
-      }
-    },
     split() {
       const map = this.openLayersMap;
       this.$nextTick(() => {
--- a/client/src/morphtool/Morphtool.vue	Wed Nov 07 15:26:46 2018 +0100
+++ b/client/src/morphtool/Morphtool.vue	Mon Nov 12 14:45:07 2018 +0100
@@ -104,12 +104,13 @@
  * Author(s):
  * Thomas Junk <thomas.junk@intevation.de>
  */
-import { mapState } from "vuex";
+import { mapState, mapGetters } from "vuex";
 
 export default {
   name: "morphtool",
   computed: {
-    ...mapState("map", ["drawMode"]),
+    ...mapGetters("map", ["getLayerByName"]),
+    ...mapState("map", ["openLayersMap", "cutTool"]),
     ...mapState("bottlenecks", [
       "selectedBottleneck",
       "surveys",
@@ -120,9 +121,9 @@
     clearSelection() {
       this.$store.dispatch("bottlenecks/setSelectedBottleneck", null);
       this.$store.commit("application/showSplitscreen", false);
-      if (this.drawMode) {
-        this.$store.commit("map/toggleDrawModeLine");
-      }
+      this.$store.commit("map/cutMode", false);
+      this.getLayerByName("Cut Tool").data.getSource().clear();
+      this.openLayersMap.removeInteraction(this.cutTool)
     }
   }
 };
--- a/client/src/pdftool/Pdftool.vue	Wed Nov 07 15:26:46 2018 +0100
+++ b/client/src/pdftool/Pdftool.vue	Mon Nov 12 14:45:07 2018 +0100
@@ -1,5 +1,5 @@
 <template>
-    <div class="pdftool">
+    <div class="pdftool" :style="selectedSurvey ? 'bottom: 140px' : ''">
         <div @click="$store.commit('application/showPdfTool', !showPdfTool)" class="d-flex flex-column ui-element minimizer">
             <i :class="['fa', 'mt-1', {'fa-file-pdf-o': !showPdfTool}, {'fa-close': showPdfTool}]"></i>
         </div>
@@ -44,10 +44,10 @@
 
 <style lang="scss" scoped>
 .pdftool {
-  position: relative;
+  position: absolute;
   margin-right: $offset;
   margin-bottom: $offset;
-  bottom: 48px;
+  bottom: 96px;
   right: 0;
 }
 
@@ -110,6 +110,7 @@
   },
   computed: {
     ...mapState("application", ["showPdfTool"]),
+    ...mapState("bottlenecks", ["selectedSurvey"]),
     style() {
       return {
         "ui-element": true,
--- a/client/src/store/map.js	Wed Nov 07 15:26:46 2018 +0100
+++ b/client/src/store/map.js	Mon Nov 12 14:45:07 2018 +0100
@@ -35,10 +35,12 @@
   namespaced: true,
   state: {
     openLayersMap: null,
-    identifiedFeatures: [],
-    currentMeasurement: null,
-    // there are three states of drawMode: null, "LineString", "Polygon"
-    drawMode: null,
+    identifiedFeatures: [], // map features identified by clicking on the map
+    currentMeasurement: null, // distance or area from drawTool
+    drawMode: null, // null, "LineString", "Polygon"
+    drawTool: null, // open layers interaction object (Draw)
+    cutMode: false, // true or false
+    cutTool: null, // open layers interaction object (Draw)
     layers: [
       {
         name: "Open Streetmap",
@@ -280,6 +282,54 @@
         }),
         isVisible: true,
         showInLegend: false
+      },
+      {
+        name: "Cut Tool",
+        data: new VectorLayer({
+          source: new VectorSource({ wrapX: false }),
+          style: function(feature) {
+            // adapted from OpenLayer's LineString Arrow Example
+            var geometry = feature.getGeometry();
+            var styles = [
+              // linestring
+              new Style({
+                stroke: new Stroke({
+                  color: "#369aca",
+                  width: 2
+                })
+              })
+            ];
+
+            if (geometry.getType() === "LineString") {
+              geometry.forEachSegment(function(start, end) {
+                var dx = end[0] - start[0];
+                var dy = end[1] - start[1];
+                var rotation = Math.atan2(dy, dx);
+                // arrows
+                styles.push(
+                  new Style({
+                    geometry: new Point(end),
+                    image: new Icon({
+                      // we need to make sure the image is loaded by Vue Loader
+                      src: require("../application/assets/linestring_arrow.png"),
+                      // fiddling with the anchor's y value does not help to
+                      // position the image more centered on the line ending, as the
+                      // default line style seems to be slightly uncentered in the
+                      // anti-aliasing, but the image is not placed with subpixel
+                      // precision
+                      anchor: [0.75, 0.5],
+                      rotateWithView: true,
+                      rotation: -rotation
+                    })
+                  })
+                );
+              });
+            }
+            return styles;
+          }
+        }),
+        isVisible: true,
+        showInLegend: false
       }
     ]
   },
@@ -305,15 +355,17 @@
     setCurrentMeasurement: (state, measurement) => {
       state.currentMeasurement = measurement;
     },
-    toggleDrawModeLine: state => {
-      if (state.drawMode) {
-        state.drawMode = null;
-      } else {
-        state.drawMode = "LineString";
-      }
+    drawMode: (state, mode) => {
+      state.drawMode = mode;
+    },
+    drawTool: (state, drawTool) => {
+      state.drawTool = drawTool;
     },
-    activateDrawModePolygon: state => {
-      state.drawMode = "Polygon";
-    }
+    cutMode: (state, mode) => {
+      state.cutMode = mode;
+    },
+    cutTool: (state, cutTool) => {
+      state.cutTool = cutTool;
+    },
   }
 };
--- a/client/src/usermanagement/Usermanagement.vue	Wed Nov 07 15:26:46 2018 +0100
+++ b/client/src/usermanagement/Usermanagement.vue	Mon Nov 12 14:45:07 2018 +0100
@@ -214,7 +214,7 @@
     spacerStyle() {
       return {
         spacer: true,
-        "spacer-expanded": (this.showUsermenu && this.showSidebar),
+        "spacer-expanded": this.showUsermenu && this.showSidebar,
         "spacer-collapsed": !this.showUsermenu && this.showSidebar
       };
     },