SpeciesFileGroup/taxonworks

View on GitHub
app/javascript/vue/components/Filter/Facets/shared/FacetGeographic.vue

Summary

Maintainability
Test Coverage
<template>
  <FacetContainer>
    <h3>Geographic area</h3>
    <VSwitch
      class="separate-bottom"
      v-model="view"
      :options="Object.values(TABS)"
    />
    <div v-if="view === 'area'">
      <div class="field">
        <VAutocomplete
          :input-id="inputId"
          url="/geographic_areas/autocomplete"
          label="label_html"
          clear-after
          placeholder="Search a geographic area"
          param="term"
          @get-item="({ id }) => addGeoArea(id)"
        />
      </div>

      <ul class="no_bullets">
        <li
          v-for="(value, key) in GEOGRAPHIC_OPTIONS"
          :key="key"
        >
          <label>
            <input
              type="radio"
              :value="value"
              v-model="geographic.geographic_area_mode"
            />
            {{ key }}
          </label>
        </li>
      </ul>

      <div class="field separate-top">
        <ul class="no_bullets table-entrys-list">
          <li
            class="middle flex-separate list-complete-item"
            v-for="(geoArea, index) in geographicAreas"
            :key="geoArea.id"
          >
            <span
              :class="{
                subtle:
                  geographic.geographic_area_mode ===
                    GEOGRAPHIC_OPTIONS.Spatial && !geoArea.has_shape
              }"
              v-html="geoArea.name"
            />
            <VBtn
              circle
              color="primary"
              @click="removeGeoArea(index)"
            >
              <VIcon
                x-small
                name="trash"
              />
            </VBtn>
          </li>
        </ul>
      </div>
    </div>
    <VMap
      v-else
      width="100%"
      height="300px"
      :geojson="geojson"
      draw-controls
      :draw-polyline="false"
      :draw-marker="false"
      :drag-mode="false"
      :cut-polygon="false"
      :draw-circle-marker="false"
      :tiles-selection="false"
      :edit-mode="false"
      :zoom="1"
      @geojson="geojson = $event"
      @geo-json-layer-created="addShape"
    />
    <RadialFilterAttribute
      :parameters="{ geographic_area_id: geographic.geographic_area_id }"
    />
  </FacetContainer>
</template>

<script setup>
import VSwitch from '@/components/ui/VSwitch'
import VAutocomplete from '@/components/ui/Autocomplete'
import VMap from '@/components/georeferences/map'
import RadialFilterAttribute from '@/components/radials/linker/RadialFilterAttribute.vue'
import FacetContainer from '@/components/Filter/Facets/FacetContainer.vue'
import VBtn from '@/components/ui/VBtn/index.vue'
import VIcon from '@/components/ui/VIcon/index.vue'
import { GeographicArea } from '@/routes/endpoints'
import { computed, ref, watch, onBeforeMount } from 'vue'

const TABS = {
  Area: 'area',
  Map: 'map'
}

const GEOGRAPHIC_OPTIONS = {
  Spatial: true,
  Descendants: false,
  Exact: undefined
}

const props = defineProps({
  modelValue: {
    type: Object,
    required: true
  },
  inputId: {
    type: String,
    default: undefined
  }
})

const emit = defineEmits(['update:modelValue'])

const geographic = computed({
  get: () => props.modelValue,
  set: (value) => emit('update:modelValue', value)
})
const geographicAreas = ref([])
const geojson = ref([])
const view = ref(TABS.Area)

watch(
  geojson,
  (newVal) => {
    if (newVal.length) {
      const shape = newVal[0]

      geographic.value.geographic_area_id = []

      if (shape.properties?.radius) {
        geographic.value.radius = shape.properties.radius
        geographic.value.geo_json = JSON.stringify({
          type: 'Point',
          coordinates: shape.geometry.coordinates
        })
      } else {
        geographic.value.geo_json = JSON.stringify({
          type: 'MultiPolygon',
          coordinates: newVal.map((feature) => feature.geometry.coordinates)
        })
        geographic.value.radius = undefined
      }
    } else {
      geographic.value.geo_json = []
    }
  },
  { deep: true }
)

watch(geographic, (newVal) => {
  if (!newVal?.length) {
    geographicAreas.value = []
  }
})

const addShape = (shape) => {
  geojson.value = [shape]
}

const removeGeoArea = (index) => {
  geographicAreas.value.splice(index, 1)
}

const addGeoArea = (id) => {
  GeographicArea.find(id).then((response) => {
    geographic.value.geo_json = undefined
    geographic.value.radius = undefined
    geographicAreas.value.push(response.body)
  })
}

const convertGeoJSONParam = (urlParams) => {
  const geojson = JSON.parse(urlParams.geo_json)

  return {
    type: 'Feature',
    geometry: {
      coordinates:
        geojson.type === 'Point' ? geojson.coordinates : geojson.coordinates[0],
      type: geojson.type === 'Point' ? 'Point' : 'Polygon'
    },
    properties: {
      radius: urlParams?.radius
    }
  }
}

watch(
  () => geographic.value.geo_json,
  (newVal, oldVal) => {
    if (!newVal?.length && oldVal?.length) {
      geojson.value = []
    }
  },
  { deep: true }
)

watch(
  [geographicAreas, () => geographic.value.geographic_area_mode],
  () => {
    geographic.value.geographic_area_id =
      geographic.value.geographic_area_mode === GEOGRAPHIC_OPTIONS.Spatial
        ? geographicAreas.value
            .filter((item) => item.has_shape)
            .map((item) => item.id)
        : geographicAreas.value.map((item) => item.id)
  },
  { deep: true }
)

watch(
  () => geographic.value.geographic_area_id,
  (newVal, oldVal) => {
    if (!newVal?.length && oldVal?.length) {
      geographicAreas.value = []
    }
  },
  { deep: true }
)

onBeforeMount(() => {
  if (geographic.value.geographic_area_id) {
    [geographic.value.geographic_area_id].flat().forEach((id) => {
      addGeoArea(id)
    })
  }
  if (geographic.value.geo_json) {
    addShape(convertGeoJSONParam(geographic.value))
  }
})
</script>
<style scoped>
:deep(.vue-autocomplete-input) {
  width: 100%;
}
</style>