Source

components/shared/MapToolbox/MapToolbox.vue

<template lang="pug">
//portal(to="MapToolboxPortalTarget")
.map-toolbox(ref="root" v-show="isVisible" :style="rootStyle" :data-id="_uid")
  .map-toolbox-content
    .map-toolbox-options(v-show="!isCollapsed")

      .map-toolbox-option(
        v-if="hasAccessRightToMapPredefinedViews"
        @click="onFeatureLinkClick('predefined_views')",
        :class="{ 'mto-selected': selectedItem === 'predefined_views' }"
        v-b-tooltip.hover.viewport,
          :title="$t('geocoding.tool_title.predefined_views')"
      )
        Icon(icon="carbon:zoom-area" :style="{fontSize:toolbarIconSize}" 
        :color="selectedItem === 'predefined_views' ? 'white' : colors.color_main")


      .map-toolbox-option(
        @click="onFeatureLinkClick('forward')",
        :class="{ 'mto-selected': selectedItem === 'forward' }"
        v-b-tooltip.hover.viewport
          :title="$t('geocoding.tool_title.locate_address')"
      )
        GeocodingForwardFormIcon(
          :fillColor="selectedItem === 'forward' ? 'white' : colors.color_main",
          :size="toolbarIconSize"
        )
      .map-toolbox-option(
        @click="onFeatureLinkClick('forwardNatural')",
        :class="{ 'mto-selected': selectedItem === 'forwardNatural' }"
        v-b-tooltip.hover.viewport,
          :title="$t('geocoding.tool_title.locate_address_natural')",
      )
        GeocodingForwardNaturalIcon(
          :fillColor="selectedItem === 'forwardNatural' ? 'white' : colors.color_main",
          :size="toolbarIconSize"
        )
      .map-toolbox-option(
        @click="onFeatureLinkClick('reverse')",
        :class="{ 'mto-selected': selectedItem === 'reverse' }"
        v-b-tooltip.hover.viewport,
          :title="$t('geocoding.tool_title.locate_address_latlng')",
      )
        GeocodingReverseIcon(
          :fillColor="selectedItem === 'reverse' ? 'white' : colors.color_main",
          :size="toolbarIconSize"
        )
      .map-toolbox-option(
        @click="onFeatureLinkClick('routing')",
        :class="{ 'mto-selected': selectedItem === 'routing' }"
        v-b-tooltip.hover.viewport,
          :title="$t('geocoding.tool_title.routing')"
      )
        GeocodingRoutingIcon(
          :fillColor="selectedItem === 'routing' ? 'white' : colors.color_main",
          :size="toolbarIconSize"
          aria-label=""
          title=""
        )
      
      .map-toolbox-option(
        @click="onFeatureLinkClick('circuit_reference')",
        :class="{ 'mto-selected': selectedItem === 'circuit_reference' }"
        v-b-tooltip.hover.viewport,
          :title="$t('reference_circuit.title')"
      )
        RoadIcon(
          :fillColor="selectedItem === 'circuit_reference' ? 'white' : colors.color_main",
          :size="toolbarIconSize"
          aria-label=""
          title=""
        )
    MapToolboxContent(
      v-if="!!selectedItem",
      v-show="!isCollapsed",
      :selectedItem="selectedItem"
    )
  .map-toolbox-toggle-wrapper
    .map-toolbox-toggle-button(@click="onPanelClick"
      v-b-tooltip.hover.viewport,
    :title="$t('common.map_toolbox.toggle_button_tooltip')"
    )
      span.iconify(
        data-icon="mdi:toolbox-outline",
        data-inline="false",
        :style="`color:${colors.color_main}`",
        data-width="25",
        data-height="25"
      )
</template>
<script>
import GeocodingForwardFormIcon from 'vue-material-design-icons/HomeMapMarker.vue'
import GeocodingForwardNaturalIcon from 'vue-material-design-icons/MapMarker.vue'
import GeocodingReverseIcon from 'vue-material-design-icons/Compass.vue'
import GeocodingRoutingIcon from 'vue-material-design-icons/MapMarkerPath.vue'
import { Icon } from '@iconify/vue2'
import RoadIcon from 'vue-material-design-icons/Road.vue'
import MapToolboxContent from '@c/shared/MapToolbox/MapToolboxContent.vue'
import colors from '@/styles/colors'
import { isUnitTest } from '@/utils/unit.js'
import { removeNativeTooltipFromMaterialIconsMixin } from '@/mixins/icons-mixin.js'
import { useMouseInOutDelay } from '@/composables/mouse.ts'
import { ref, watch, computed, watchEffect } from 'vue'
import { useElementStyle } from '@vueuse/motion'
import { layoutStaticContext } from '@/components/shared/TLayout/TLayout.vue'
import { useRightsPlugin } from '@/mixins/rights-mixin.js'
import { useMapContextMenu } from '@/components/shared/SimplicitiMap/MapContextMenu.vue'

const { menuContextSelectedOption } = useMapContextMenu()

const { bindMouseInOutDelay, unbindMouseInOutDelay } = useMouseInOutDelay()

const zIndex = ref(1000000)

const { hasFeatureRight, rightsTable } = useRightsPlugin()

/**
 *
 * This was added to prevent the map toolbox from overlapping on top of other components (because of his position being absolute).
 *
 * Faq: This map toolbox is associated to the map, why is being present when the map is hidden? Because this component is wrapped in a portal (portal-vue)
 *
 * Used by Location (Full table mode)
 * Used by Routing (Full table mode)
 *
 * Svelte module context patron: https://svelte.dev/tutorial/module-exports
 */
export function setLowerMapToolboxZIndex() {
  console.log('setLowerMapToolboxZIndex')
  zIndex.value = 100000
}

/**
 * Svelte module context patron: https://svelte.dev/tutorial/module-exports
 */
export function setHigherMapToolboxZIndex() {
  console.log('setHigherMapToolboxZIndex')
  zIndex.value = 1000000
}

let createExitMixin = () => {}
if (!isUnitTest()) {
  createExitMixin = require('@/mixins/exit-mixin.ts').default
}

export const MapToolboxIsolatedConfig = [
  'MapToolbox',
  async () => ({
    name: 'MapToolboxIsolated',
    components: {
      SimplicitiMap,
      //MapToolbox,
      MapToolboxContent,
    },
    template: `<div>><MapToolbox/><MapToolboxContent/></div>`,
  }),
  {
    props: {
      //Custom props
    },
    beforeEnter: (to, from, next) => {
      //Custom logic before routing in
      return !!next && next()
    },
  },
]

/**
 * Map toolbox in the top-right corner.
 *
 * Allows access to Geocoding tools and other map features such as reference circuit, missio model, last pass-by vehicles, etc.
 *
 * Develop in isolated mode (Will full HMR):
 * http://georedv3:8082/#/components/MapToolbox?mockapp=1
 *
 * https://xd.adobe.com/view/00faf07a-1004-4873-89b8-45fe02de01f2-55a9/screen/41ae22cd-20f0-4c0e-adfa-d73a95171395
 */
export default {
  name: 'MapToolbox',
  components: {
    GeocodingForwardFormIcon,
    GeocodingReverseIcon,
    GeocodingForwardNaturalIcon,
    GeocodingRoutingIcon,
    RoadIcon,
    MapToolboxContent,
    Icon,
  },
  mixins: [
    createExitMixin({
      attributeName: 'isCollapsed',
      bindEscape: true,
    }),
    removeNativeTooltipFromMaterialIconsMixin,
  ],
  provide() {
    let self = this

    const mapToolboxContext = {
      toogleMouseInOutDelayPauseResume: () =>
        self.mouseInOutDelayController.tooglePauseResume(),
      setContentPanelVisible: (isVisible) => {
        self.isCollapsed = !isVisible
        console.log('setContentPanelVisible', {
          isVisible,
          isCollapsed: self.isCollapsed,
        })
      },
    }
    Object.defineProperties(mapToolboxContext, {
      isMouseInOutDelayPaused: {
        get() {
          return self.mouseInOutDelayController.isPaused()
        },
      },
    })

    return {
      mapToolboxContext,
      disableDatatableSetHeightFromParent: computed(() => {
        return self.isCollapsed ? true : false
      }),
      /**
       * Prevent child maps to re-compute their size if hidden
       */
      disableLeafletMapInvalidateSize: computed(() => {
        return self.isCollapsed ? true : false
      }),
    }
  },
  data() {
    return {
      isCollapsed: true,
      selectedItem: '',
      rootStyleRightValue: 0.5,
    }
  },
  computed: {
    hasAccessRightToMapPredefinedViews() {
      return hasFeatureRight(rightsTable.map_predefined_view_list)
    },

    toolbarIconSize() {
      return 20
    },
    colors: () => colors,
    /***
     * 31655: We will hide the toolbox if Map+Table to avoid issues (Not enough visual space) (i.g Location module)
     */
    isVisible() {
      let data = layoutStaticContext.$data
      if (data.isRightMenuVisible && data.isRightMenuBottomVisible) {
        return false
      }
      return true
    },
    rootStyleRight() {
      return `${this.rootStyleRightValue}px`
    },
    rootStyle() {
      return `right: ${this.rootStyleRight}`
    },
  },
  mounted() {
    this.bindMouseInOutDelayInternal()

    console.log('MapToolbox::mounted', this._uid)

    this.bindModuleZIndexToRootStyle()
    setHigherMapToolboxZIndex()
    instances.value.push(this)
  },
  destroyed() {
    instances.value.pop()
    unbindMouseInOutDelay(this.$refs.root)
  },
  methods: {
    /**
     * zIndex is controlled by a global/static ref (Module context patron)
     */
    bindModuleZIndexToRootStyle() {
      if (!this.$refs.root) {
        return setTimeout(() => this.bindModuleZIndexToRootStyle(), 250) //Wait for root ref
      }

      watch(
        zIndex,
        () => {
          const { style, stop } = useElementStyle(ref(this.$refs.root))
          this._rootStyle = style

          style.zIndex = zIndex.value

          console.log(
            'bindModuleZIndexToRootStyle::' + this._uid + '::update',
            zIndex.value
          )
        },
        {
          immediate: true,
        }
      )
    },
    /**
     * Automatically reduce window size if mouseout for a certain time
     */
    bindMouseInOutDelayInternal() {
      let self = this
      let el = () => this.$refs.root
      let defaultRootStyleRightValue = 0.5
      self.mouseInOutDelayController = bindMouseInOutDelay(el, {
        delayTimeoutMilli: 1000 * 3,
        onMouseIn() {
          self.rootStyleRightValue = defaultRootStyleRightValue
        },
        onMouseOutDelay: () => {
          if (!el()) {
            return //Component has been destroyed
          }
          const shouldCollapse = self.selectedItem === 'circuit_reference'
          const collapsedValue = (el().offsetWidth / 2 - 55) * -1
          self.rootStyleRightValue = shouldCollapse
            ? collapsedValue
            : defaultRootStyleRightValue
        },
      })
    },
    onPanelClick() {
      this.isCollapsed = !this.isCollapsed
    },
    onFeatureLinkClick(name) {
      this.selectedItem = name
    },
  },
}

const instances = ref([])

watchEffect(watchAndReactToMapMenuContextOptions)

/**
 * - Will open reverse geocoding view automatically
 */
function watchAndReactToMapMenuContextOptions() {
  let vm =
    instances.value.length > 0
      ? instances.value[instances.value.length - 1]
      : null
  if (vm) {
    if (menuContextSelectedOption.value === 'nearbyVehicles') {
      openMapToolboxFeature('reverse')
    }
  }
}

/**
 * Used internally to open reverse geocoding view if map menu context option "nearby vehicles" is clicked
 * @param {*} featureName
 */
function openMapToolboxFeature(featureName) {
  let vm = instances.value[instances.value.length - 1]
  vm.isCollapsed = false //Open toolbox programatically
  vm.onFeatureLinkClick(featureName)
}

/**
 * @warn unused
 */
export function useMapToolbox() {
  return {
    openMapToolboxFeature,
  }
}
</script>
<style lang="scss" scoped>
.map-toolbox {
  position: absolute;
  top: 0px;
  /*right: 5px;*/
  transition: right 0.8s;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.map-toolbox-content {
  min-width: 465px;
  background-color: white;

  border-radius: 0px 0px 10px 10px;
}
.map-toolbox-toggle-wrapper {
  background: #ffffff 0% 0% no-repeat padding-box;
  box-shadow: 0px 2px 3px rgb(0 0 0 / 36%);
  border-radius: 0px 0px 10px 10px;
  padding: 5px;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  width: 94px;
}
.map-toolbox-toggle-button {
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
}
.map-toolbox-options {
  display: flex;
  align-items: center;
  column-gap: 10px;
  justify-content: space-around;
  padding: 5px 0px;
}
.map-toolbox-option {
  cursor: pointer;
  border-radius: 50%;
  padding: 5px;
  width: 30px;
  height: 30px;
  display: flex;
  justify-content: center;
  align-items: center;
}

.mto-selected {
  background: var(--color-main);
}
</style>