Source

components/location/MapOptions/MapOptions.vue

<template lang="pug">
.map_options(:class="{unfold:!collapsed}" )
    .map_options__inner(v-show="showMapOptions")
        .toggle_button(@click="collapsed=!collapsed")
            span {{$t('common.map.options.title')}}&nbsp;
            em.fas.fa-chevron-up(v-show="!collapsed")
            em.fas.fa-chevron-down(v-show="collapsed")
    .map_options_content(v-show="showMapOptions && !collapsed")
      .row.m-0.map_options_content_layout
        .map_options_content__left
          LinestringSelect(
            v-if="isCircuitMap===false && hasSpeedPolylines"
            v-model="linestringType" :showSpeedPolylinesOption="hasSpeedPolylines")
          PositionsToggleButton(
              v-show="showPositionsLayerToggle"
              v-model="positions")
          InlineToggleButton(
              v-show="showTripHistoryPolylinesLayerToggle"
              v-model="tripHistoryPolylines"
              :text="$t('common.map.options.trip_history__label')"
              )
            template(v-slot:icon="")
              HistoryTraceIcon(color="var(--color-dark-blue)")
          ContactToggleButton(
            :text="$t('location.history_tab.item.contact_on')"
            :color="'var(--color-silver-tree)'"
            v-show="showContactON"
            v-model="contactOn")
          ContactToggleButton(
            :text="$t('location.history_tab.item.contact_off')"
            :color="'var(--color-coral-red)'"
            v-show="showContactOFF"
            v-model="contactOff")
          InlineToggleButton(
              v-show="showCircuitPolylinesLayerToggle"
              v-model="circuitPolylines"
              :text="$t('common.Circuit')"
              )
            template(v-slot:icon="")
              CircuitIcon(color="var(--color-dark-blue)")
          ArrowsToggleButton(
            :text="$t('map_options.trip_history_arrows')"
            v-show="hasLeafletLayerGroupWithName('circuit_execution_arrows')"
            v-model="arrows"
          )
          InlineToggleButton(
              v-show="eventsMarkers.length>0"
              v-model="eventsMarkersToggle"
              :text="$t('common.Événements')"
              )
            template(v-slot:icon="")
              EventsIcon(color="var(--color-sandy-brown)")
          LinestringsToggleButton(
              v-show="alertsMarkers.length>0"
              v-model="alertsMarkersToggle"
              :text="$t('common.Alertes')"
              )
          InlineToggleButton(
              v-show="identificationBacsMarkers.length>0"
              v-model="bacsMarkersToggle"
              :text="$t('common.map.options.identification')"
              )
            template(v-slot:icon="")
              IdentBacsIcon
          InlineToggleButton(
              v-show="shouldShowZonesToogle"
              v-model="zonesMarkersToggle"
              :text="$t('common.Zones')"
              )
            template(v-slot:icon="")
              ZonesIcon(
                class="mr-1"
                color="var(--color-dark-blue)"
                )
          InlineToggleButton(
              v-show="chronoMarkers.length>0"
              v-model="chronoMarkersToggle"
              :text="'Imputations chrono'"
              )
            template(v-slot:icon="")
              ChronoIcon(
                class="mr-1"
                color="var(--color-dark-blue)"
                :size="24"
                )
          
          .dynamic-section(v-for="item in $store.state.map_options.sections" :key="item.uniqueName")
            component(
              v-show="dynamicSectionsVisibility[item.uniqueName]"
              :is="item.componentToRender" v-model="dynamicSectionsState[item.uniqueName]" :text="$t(item.i18nLabel)")
              template(v-slot:icon="")
                component(v-if="item.materialIcon" :is="item.materialIcon"
                  class="mr-1"
                  fillColor="var(--color-dark-blue)"
                  :size="24" 
                )

          SpeedFilter(v-if="isCircuitMap===false && hasSpeedPolylines && positions===true" v-model="speedFilter" @change="speedFilterValueChange")
        .map_options_content__center
          SensorsConfig(v-show="!!sensorsConfig.id" :config="sensorsConfig"
          @update="updateSensorConfig"
          )
        .map_options_content__right
          ToggleButton(:value="true" @click="showAllLayers") {{$t('common.map.options.all')}}
          ToggleButton(:value="false" @click="hideAllLayers") {{$t('common.map.options.none')}}
      MapCircuitLegends(v-show="showCircuitActivitiesLegend")
            
</template>
<script>
import PositionsToggleButton from './PositionsToggleButton.vue'
import LinestringsToggleButton from './LinestringsToggleButton.vue'
import ContactToggleButton from './ContactToggleButton.vue'
import LinestringSelect from './LinestringSelect.vue'
import SensorsConfig from './SensorsConfig/SensorsConfig.vue'
import SpeedFilter from './SpeedFilter.vue'
import ToggleButton from './ToggleButton.vue'
import MapCircuitLegends from './MapCircuitLegends.vue'
import ArrowsToggleButton from '@c/location/MapOptions/ArrowsToggleButton.vue'
import Vue from 'vue'
import { mapGetters } from 'vuex'
import InlineToggleButton from '@c/location/MapOptions/InlineToggleButton.vue'
import CircuitIcon from '@c/shared/icons/CircuitIcon.vue'
import HistoryTraceIcon from '@c/shared/icons/HistoryTraceIcon.vue'
import ZonesIcon from '@c/shared/icons/ZonesIcon.vue'
import EventsIcon from '@c/shared/icons/EventsIcon.vue'
import IdentBacsIcon from '@c/shared/icons/IdentBacsIcon.vue'
import ChronoIcon from '@c/location/LocationChrono/assets/ChronoIcon.vue'
import MapMarkerIcon from 'vue-material-design-icons/MapMarker.vue'
import { removeNativeTooltipFromMaterialIconsMixin } from '@/mixins/icons-mixin.js'
/**
 * @typedef {Object} dynamicToggleControlSectionsMixin
 * @description
 * Dynamic toggle control sections allow us to define toggle controls once.
 * Each toggle control will automatically show/hide itself and show/hide related leaflet layers.
 * @todo Refactor existing layers toggles (i.g zone) into a dynamic section
 * Note: Add extra sections in the related vuex store "map_options/index.js"
 */
const dynamicToggleControlSectionsMixin = {
  data() {
    return {
      dynamicSectionsState: {},
      dynamicSectionsVisibility: {},
    }
  },
  watch: {
    /**
     * React to manual/auto toggle
     */
    dynamicSectionsState: {
      handler() {
        Object.keys(this.dynamicSectionsState).forEach((key) => {
          this.updateDynamicSectionRelatedLeafletLayersGroupsVisibility(
            key,
            this.dynamicSectionsState[key]
          )
        })
      },
      deep: true,
    },
  },
  methods: {
    /**
     * Toggle all on/off support
     */
    toggleDynamicSectionsControls(areEnabled) {
      Object.keys(this.dynamicSectionsState).forEach((key) => {
        this.dynamicSectionsState[key] = areEnabled
      })
    },
    /**
     * @example
     * i.g Routing results (geocoding_routing_instructions) will toggle to layers: start/end markers and result polyline.
     */
    updateDynamicSectionRelatedLeafletLayersGroupsVisibility(
      uniqueName,
      isEnabled
    ) {
      let section = this.$store.state.map_options.sections.find(
        (s) => s.uniqueName == uniqueName
      )
      if (section.willToggleLeafletMapLayerGroups) {
        section.willToggleLeafletMapLayerGroups.forEach((layerGroupName) => {
          this.toggleLayer(layerGroupName, isEnabled)
        })
      }
    },
    /**
     * Workaround to keep the toggle buttons visibility in sync with the layer groups available in leaflet
     * (Only for showRule "layerGroupExists" and sections with "willToggleLeafletMapLayerGroups" supplied)
     */
    bindCheckDynamicSectionsVisibilityInterval() {
      if (this._checkDynamicSectionsVisibilityInterval) {
        this.unbindCheckDynamicSectionsVisibilityInterval()
      }
      this._checkDynamicSectionsVisibilityInterval = setInterval(() => {
        return this.$store.state.map_options.sections.forEach((section) => {
          let isDynamicSectionVisible = false

          if (
            section.showRule === 'layerGroupExists' &&
            !!section.willToggleLeafletMapLayerGroups.find((n) =>
              this.hasLeafletLayerGroupWithName(n)
            )
          ) {
            isDynamicSectionVisible = true
          }

          this.$set(
            this.dynamicSectionsVisibility,
            section.uniqueName,
            isDynamicSectionVisible
          )
        })
      }, 2000)
    },
    unbindCheckDynamicSectionsVisibilityInterval() {
      clearInterval(this._checkDynamicSectionsVisibilityInterval)
      this._checkDynamicSectionsVisibilityInterval = null
    },
    /**
     * Dynamic state need to be set with this.$set / Vue.$set
     */
    initializeDynamicSectionsState() {
      this.$store.state.map_options.sections.forEach((section) => {
        if (
          typeof this.dynamicSectionsState[section.uniqueName] === 'undefined'
        ) {
          const initialValue =
            section.initialValue !== undefined ? section.initialValue : false
          this.$set(this.dynamicSectionsState, section.uniqueName, initialValue)
        }
      })
    },
  },
}

/**
 * @vue-event {Number} toggleAllLayers - The user has click Show All/None buttons
 */
export default {
  components: {
    PositionsToggleButton,
    LinestringsToggleButton,
    ContactToggleButton,
    LinestringSelect,
    SensorsConfig,
    SpeedFilter,
    ToggleButton,
    MapCircuitLegends,
    ArrowsToggleButton,
    InlineToggleButton,
    CircuitIcon,
    HistoryTraceIcon,
    ZonesIcon,
    EventsIcon,
    IdentBacsIcon,
    ChronoIcon,
    MapMarkerIcon,
  },
  mixins: [
    dynamicToggleControlSectionsMixin,
    removeNativeTooltipFromMaterialIconsMixin,
  ],
  componentType: 'container',
  inject: {
    simplicitiMapName: {
      default: '',
    },
    isTripHistoryTableMap: {
      default: false,
    },
    isLocationMainSearch: {
      default: false,
    },
    isCircuitMap: {
      default: false,
    },
    isEventsMap: {
      default: false,
    },
    isAlertsMap: {
      default: false,
    },
    isIdentificationBacsMap: {
      default: false,
    },
    isZonesMap: {
      default: false,
    },
    isChronoMap: {
      default: false,
    },
    showGPSPositionMarkersWhenAvailable: {
      default: () => ({}),
    },
  },
  props: {
    /**
     * One way binding (toggle values are exposed to parent, i.g: {positions:false} )
     */
    value: {
      type: Object,
      default: () => ({}),
    },
    initialContactCheckboxesValue: {
      type: Boolean,
      default: false,
    },
    initialSensorsVisibilityValue: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    return {
      locateAddressMarkerToggle: true,
      chronoMarkersToggle: this.isChronoMap,
      eventsMarkersToggle: this.isEventsMap,
      alertsMarkersToggle: this.isAlertsMap,
      bacsMarkersToggle: this.isIdentificationBacsMap,
      zonesMarkersToggle: false,
      collapsed: true,
      positions: this.isTripHistoryTableMap || this.isCircuitMap,
      circuitPolylines: !this.isLocationMainSearch && this.isCircuitMap,
      tripHistoryPolylines: !this.isLocationMainSearch && !this.isCircuitMap,
      contactOff: this.initialContactCheckboxesValue,
      contactOn: this.initialContactCheckboxesValue,
      linestringType: 'normal',
      speedFilter: {
        value: '',
      },
      alerts: false,
      arrows: false,
      hasLocateAddressMarker: false,
    }
  },
  computed: {
    ...mapGetters({
      vehiclePositionMarkers: 'simpliciti_map/vehiclePositionMarkers',
      singleCircuitExecPolylines: 'simpliciti_map/singleCircuitExecPolylines',
      singleTripHistoryPolylines: 'simpliciti_map/singleTripHistoryPolylines',
      sensorsConfig: 'map_options/sensorsConfig',
      speedPolylines: 'simpliciti_map/speedPolylines',
      eventsMarkers: 'simpliciti_map/eventsMarkers',
      alertsMarkers: 'simpliciti_map/alertsMarkers',
      identificationBacsMarkers: 'simpliciti_map/identificationBacsMarkers',
      zonesMarkers: 'zones/clientZones',
      chronoMarkers: 'simpliciti_map/chronoMarkers',
    }),
    /**
     * Disable zones toogle (Generic zones on any map) if current module is zones (To avoid duplicated zones)
     */
    shouldShowZonesToogle() {
      return this.zonesMarkers.length > 0 && !this.isZonesMap
    },
    /**
     * Show only if there is any geometry to toggle on/off
     */
    showMapOptions() {
      //Trip history positions speed filter
      if (this.isCircuitMap === false && this.hasSpeedPolylines) {
        return true
      }
      //Positions
      if (this.showPositionsLayerToggle) {
        return true
      }
      //Trip history polylines
      if (this.showTripHistoryPolylinesLayerToggle) {
        return true
      }
      //Trip history contact on/off markers
      if (this.showContactON || this.showContactOFF) {
        return true
      }
      //Circuit polylines
      if (this.showCircuitPolylinesLayerToggle) {
        return true
      }
      //Events
      if (this.eventsMarkers.length > 0) {
        return true
      }
      //Alerts
      if (this.alertsMarkers.length > 0) {
        return true
      }
      //Bacs
      if (this.identificationBacsMarkers.length > 0) {
        return true
      }
      //Zones
      if (this.shouldShowZonesToogle) {
        return true
      }
      //Chrono (?)
      if (this.chronoMarkers.length > 0) {
        return true
      }
      //Trip history speed polylines
      if (
        this.isCircuitMap === false &&
        this.hasSpeedPolylines &&
        this.positions === true
      ) {
        return true
      }
      //Sensor config
      if (this.sensorsConfig.id) {
        return true
      }
      //Circuit legends
      if (this.showCircuitActivitiesLegend) {
        return true
      }
      //Dynamic sections
      let hasDynamicSections =
        Object.keys(this.dynamicSectionsVisibility).findIndex(
          (k) => this.dynamicSectionsVisibility[k] === true
        ) !== -1
      if (hasDynamicSections) {
        return true
      }

      return false
    },
    showContactON() {
      return (
        !this.isLocationMainSearch &&
        this.$store.getters['simpliciti_map/hasTripStepDataOfType'](
          'contact_on'
        )
      )
    },
    showContactOFF() {
      return (
        !this.isLocationMainSearch &&
        this.$store.getters['simpliciti_map/hasTripStepDataOfType'](
          'contact_off'
        )
      )
    },
    showCircuitActivitiesLegend() {
      return (
        (
          this.$store.getters['location_module/circuitExecutionSteps']
            .troncons || []
        ).length > 0
      )
    },
    showPositionsLayerToggle() {
      return this.vehiclePositionMarkers.length > 0
    },
    showTripHistoryPolylinesLayerToggle() {
      return this.singleTripHistoryPolylines.length > 0
    },
    showCircuitPolylinesLayerToggle() {
      return this.singleCircuitExecPolylines.length > 0
    },
    hasSpeedPolylines() {
      return this.speedPolylines.length > 0
    },
    sensorsConfig() {
      return this.$store.getters['map_options/sensorsConfig']
    },
    hasArrows() {
      return (
        this.$store.getters['simpliciti_map/circuitExecutionsPolylines']
          .length > 0
      )
    },
    hideTripHistoryPolylinesIfCircuitTab() {
      return this.$store.state.location_module
        .hideTripHistoryPolylinesIfCircuitTab
    },
  },
  watch: {
    hideTripHistoryPolylinesIfCircuitTab() {
      if (
        this.isCircuitMap &&
        this.hideTripHistoryPolylinesIfCircuitTab === false
      ) {
        this.tripHistoryPolylines = true
      }
    },
    showGPSPositionMarkersWhenAvailable() {
      this.positions = this.showGPSPositionMarkersWhenAvailable.value
    },

    contactOn() {
      this.toggleLayer('trip_steps_contact_on', this.contactOn)
    },
    contactOff() {
      this.toggleLayer('trip_steps_contact_off', this.contactOff)
    },
    positions: {
      handler() {
        //this.$emit("positions", this.positions);
        this.toggleLayer('positions', this.positions)
      },
      immediate: true,
    },
    tripHistoryPolylines() {
      this.updateTripHistoryPolylinesType()
    },
    circuitPolylines() {
      this.toggleLayer('circuitPolylines', this.circuitPolylines)
    },
    linestringType() {
      this.tripHistoryPolylines = true
      this.updateTripHistoryPolylinesType()
    },
    locateAddressMarkerToggle() {
      this.toggleLayer('locateAddressMarker', this.locateAddressMarkerToggle)
    },
    chronoMarkersToggle() {
      this.toggleLayer('vehicle_chrono_messages', this.chronoMarkersToggle)
    },
    eventsMarkersToggle() {
      this.toggleLayer('vehicle_events', this.eventsMarkersToggle)
    },
    alertsMarkersToggle() {
      this.toggleLayer('vehicle_alerts_messages', this.alertsMarkersToggle)
    },
    bacsMarkersToggle() {
      this.toggleLayer('bacs_markers', this.bacsMarkersToggle)
    },
    zonesMarkersToggle() {
      if (this.zonesMarkersToggle) {
        this.toggleLayer('clientZones', this.zonesMarkersToggle)
      } else {
        this.toggleLayer('clientZones', this.zonesMarkersToggle)
      }

      this.$emit('zonesMarkersToggle', this.zonesMarkersToggle)
    },
    arrows() {
      this.toggleLayer('circuit_execution_arrows', this.arrows)
    },
    hasArrows() {
      this.arrows = this.hasArrows
    },
  },
  created() {
    this.initializeDynamicSectionsState()
  },
  mounted() {
    if (this.initialSensorsVisibilityValue === false) {
      this.$store.dispatch('map_options/toggleSensorsConfigVisibility', false)
    }

    this.$watch(
      '$data',
      () => {
        let payload = Object.keys(this.$data)
          .filter((k) => !['collapsed'].includes(k))
          .reduce((a, v) => {
            a[v] = this.$data[v]
            return a
          }, {})
        this.$emit('input', payload)
      },
      {
        deep: true,
      }
    )

    this.bindCheckDynamicSectionsVisibilityInterval()
  },
  destroyed() {
    this.unbindCheckDynamicSectionsVisibilityInterval()
  },
  methods: {
    /**
     * Based on "tripHistoryPolylines", the polyline will be normal (blue), speed (colored based on speed) or hidden.
     */
    updateTripHistoryPolylinesType() {
      if (this.tripHistoryPolylines) {
        this.toggleLayer(
          'tripHistoryPolylines',
          this.linestringType === 'normal'
        )
        this.toggleLayer('speedPolylines', this.linestringType === 'speed')
      } else {
        this.toggleLayer('tripHistoryPolylines', false)
        this.toggleLayer('speedPolylines', false)
      }
    },
    /**
     * Toggles flag "showMarkers" for a single sensor configuration
     * Used when user toggles a single sensor layer
     */
    updateSensorConfig({ sensorName, payload }) {
      this.$store.dispatch('map_options/updateSensor', { sensorName, payload })
      this.$emit('sensors')
    },
    /**
     * Toggles flag "showMarkers" for all sensor configurations
     * Used when user toggles all map layers at once
     */
    toggleSensorsMarkers(value) {
      if (this.sensorsConfig && !!this.sensorsConfig.sensors) {
        this.$store.dispatch(
          'map_options/updateSensors',
          this.sensorsConfig.sensors.map((configItem) => {
            return {
              code: configItem.code,
              payload: {
                showMarkers: value,
              },
            }
          })
        )
      }
      this.$emit('sensors')
    },
    /**
     * Used when user toggles all map layers at once
     */
    toggleLayers(value) {
      this.toggleSensorsMarkers(value)

      //Remove speedFilter
      if (!value) {
        this.speedFilter.value = ''
        this.$store.dispatch('map_options/setFilter', {
          name: 'speedFilter',
          condition: this.speedFilter.condition,
          value: '',
        })
      }

      //Toggle all the $data boolean properties
      Object.keys(this.$data)
        .filter((k) => !['collapsed'].includes(k))
        .forEach((key) => {
          if (typeof this.$data[key] === 'boolean') {
            this.$data[key] = value
          }
        })

      this.toggleDynamicSectionsControls(value)

      //SimplicitiMap will toggle Leaflet layers
      this.$emit('toggleAllLayers', value)
    },
    showAllLayers() {
      this.toggleLayers(true)
    },
    hideAllLayers() {
      this.toggleLayers(false)
    },
    speedFilterValueChange() {
      this.$store.dispatch('map_options/setFilter', {
        name: 'speedFilter',
        condition: this.speedFilter.condition,
        value: parseInt(this.speedFilter.value),
      })
    },
    /**
     * Toggle a layer group in LeafletMap component
     * @todo Refactor: Improve communication with LeafletMap
     */
    toggleLayer(layerName, enabled, extraOptions = {}) {
      this.$emit('toggleLayer', { layerName, enabled, ...extraOptions })
    },
    /**
     * @todo Refactor: Improve communication with LeafletMap (Remove $parent)
     */
    hasLeafletLayerGroupWithName(layerName) {
      return (
        this.$parent.getLeafletMapWrapper() &&
        this.$parent.getLeafletMapWrapper().layerGroups &&
        this.$parent.getLeafletMapWrapper().layerGroups[layerName] &&
        this.$parent.getLeafletMapWrapper().layerGroups[layerName].getLayers()
          .length > 0
      )
    },
  },
}
</script>
<style lang="scss" scoped>
.map_options {
  width: 100%;
  background: white;
  position: relative;
}
.map_options.unfold {
  z-index: 9999;
}
.map_options__inner {
  margin: 5px 15px;
}
.map_options_content {
  position: absolute;
  height: fit-content;
  width: 100%;
  z-index: 99999;
  left: 0px;
  background: white;
  padding: 10px 15px;

  display: flex;
  flex-direction: column;
  row-gap: 10px;

  & .map_options_content_layout {
    display: flex;
    column-gap: 20px;
    justify-content: space-between;
    & .map_options_content__left,
    & .map_options_content__center,
    & .map_options_content__right {
      display: flex;
      flex-wrap: wrap;
      row-gap: 3px;
      column-gap: 10px;
      flex-direction: column;
      width: fit-content;
    }
    & .map_options_content__center {
      flex-basis: calc(60%);
    }
    & .map_options_content__right {
      justify-content: flex-end;
    }
  }
}
.toggle_button {
  user-select: none;
  cursor: pointer;
  font-size: 14px;
}
</style>