SpeciesFileGroup/taxonworks

View on GitHub
app/javascript/vue/components/layout/Filter/FilterLayout.vue

Summary

Maintainability
Test Coverage
<template>
  <FilterJsonRequestPanel
    v-show="preferences.activeJSONRequest"
    class="panel content separate-bottom"
    :url="urlRequest"
  />
  <NavBar navbar-class>
    <div class="middle grid-filter__nav">
      <div class="panel content">
        <div class="flex-separate middle">
          <div class="horizontal-left-content gap-small">
            <VBtn
              color="primary"
              medium
              @click="handleClickFilterButton"
            >
              Filter
            </VBtn>
            <label>
              <input
                type="checkbox"
                v-model="appendValue"
              />
              Append
            </label>
            <ModalNestedParameters :parameters="parameters" />
            <slot name="nav-query-left" />
            <template v-if="objectType">
              <RadialFilter
                :parameters="parameters"
                :object-type="objectType"
                :disabled="!list.length"
              />
              <RadialLinker
                all
                :parameters="parameters"
                :object-type="objectType"
                :disabled="!list.length"
              />
              <RadialMassAnnotator
                :object-type="objectType"
                :parameters="parameters"
                :disabled="!list.length"
                nested-query
              />
            </template>
            <slot name="nav-query-right" />
            <span class="separate-left separate-right">|</span>
            <VBtn
              color="primary"
              class="circle-button"
              @click="emit('reset')"
            >
              <VIcon
                name="reset"
                x-small
              />
            </VBtn>
          </div>
        </div>
      </div>
      <div class="panel content">
        <div class="flex-separate">
          <slot name="nav-left" />
          <div>
            <PaginationComponent
              v-if="pagination"
              :pagination="pagination"
              @next-page="emit('nextpage', $event)"
            />
          </div>
          <PaginationCount
            v-if="pagination"
            :pagination="pagination"
            v-model="perValue"
          />
          <div class="horizontal-right-content gap-small">
            <template v-if="selectedIds">
              <RadialFilter
                :ids="selectedIds"
                :parameters="parameters"
                :disabled="!selectedIds.length"
                :object-type="objectType"
              />
              <RadialLinker
                :ids="selectedIds"
                :disabled="!selectedIds.length"
                :object-type="objectType"
              />
              <RadialMassAnnotator
                :object-type="objectType"
                :ids="selectedIds"
                :disabled="!selectedIds.length"
              />
              <RadialNavigation
                :model="objectType"
                :ids="selectedIds"
                :disabled="!selectedIds.length"
              />
            </template>
            <slot name="nav-right" />
            <span class="separate-left separate-right">|</span>
            <FilterDownload
              :list="selectedItems"
              :extend-download="extendDownload"
            />
            <span class="separate-left separate-right">|</span>
            <FilterSettings
              v-model:filter="preferences.activeFilter"
              v-model:url="preferences.activeJSONRequest"
              v-model:list="preferences.showTable"
            >
              <template #preferences-last>
                <slot name="preferences-last" />
              </template>
            </FilterSettings>
          </div>
        </div>
      </div>
    </div>
  </NavBar>
  <div
    class="grid-filter"
    :class="{ 'grid-filter--without-facets': !preferences.activeFilter }"
  >
    <div
      v-show="preferences.activeFilter"
      class="grid-filter__facets margin-medium-bottom"
    >
      <slot name="facets" />
    </div>

    <div class="full_width overflow-x-auto">
      <slot name="above-table" />
      <slot
        v-if="preferences.showTable"
        name="table"
      />
    </div>
  </div>
</template>

<script setup>
import FilterDownload from './FilterDownload.vue'
import FilterJsonRequestPanel from './FilterJsonRequestPanel.vue'
import PaginationComponent from '@/components/pagination'
import PaginationCount from '@/components/pagination/PaginationCount'
import NavBar from '@/components/layout/NavBar.vue'
import useHotkey from 'vue3-hotkey'
import platformKey from '@/helpers/getPlatformKey'
import VBtn from '@/components/ui/VBtn/index.vue'
import VIcon from '@/components/ui/VIcon/index.vue'
import RadialFilter from '@/components/radials/filter/radial.vue'
import ModalNestedParameters from '@/components/Filter/ModalNestedParameters.vue'
import RadialLinker from '@/components/radials/linker/radial.vue'
import RadialMassAnnotator from '@/components/radials/mass/radial.vue'
import FilterSettings from './FilterSettings.vue'
import RadialNavigation from '@/components/radials/MassNavigation/radial.vue'
import { ref, computed, onBeforeUnmount, reactive } from 'vue'

const props = defineProps({
  pagination: {
    type: Object,
    default: undefined
  },

  extendDownload: {
    type: Array,
    default: () => []
  },

  urlRequest: {
    type: String,
    default: ''
  },

  objectType: {
    type: String,
    default: undefined
  },

  selectedIds: {
    type: Array,
    default: undefined
  },

  facets: {
    type: Array,
    default: () => []
  },

  modelValue: {
    type: Object,
    default: () => ({})
  },

  list: {
    type: Array,
    default: () => []
  },

  append: {
    type: Boolean,
    default: false
  }
})

const preferences = reactive({
  activeFilter: true,
  activeJSONRequest: false,
  showTable: true
})

const emit = defineEmits([
  'reset',
  'filter',
  'nextpage',
  'per',
  'update:modelValue',
  'update:append'
])

const selectedItems = computed(() =>
  props.selectedIds?.length
    ? props.list.filter((item) => props.selectedIds.includes(item.id))
    : props.list
)

const appendValue = computed({
  get: () => props.append,
  set: (value) => emit('update:append', value)
})

const parameters = computed({
  get: () => props.modelValue,
  set: (value) => emit('update:modelValue', value)
})

const perValue = computed({
  get: () => parameters.value.per,
  set: (value) => {
    parameters.value.per = value
    emit('per', value)
  }
})

const hotkeys = ref([
  {
    keys: [platformKey(), 'f'],
    preventDefault: true,
    handler() {
      emit('filter')
    }
  },
  {
    keys: [platformKey(), 'r'],
    preventDefault: true,
    handler() {
      emit('reset')
    }
  }
])

function handleClickFilterButton() {
  emit('filter')
  window.scrollTo(0, 0)
}

TW.workbench.keyboard.createLegend(
  `${platformKey()}+f`,
  'Search',
  'Filter sources'
)
TW.workbench.keyboard.createLegend(
  `${platformKey()}+r`,
  'Reset task',
  'Filter sources'
)

const stop = useHotkey(hotkeys.value)

onBeforeUnmount(() => {
  stop()
})
</script>

<style scoped>
.grid-filter {
  display: grid;
  gap: 1em;
  grid-template-columns: 400px 1fr;
}

.grid-filter--without-facets {
  grid-template-columns: 1fr;
}

.grid-filter__facets {
  width: 400px;
  max-width: 400px;
  flex-direction: column;
  display: flex;
  gap: 1em;
}

.grid-filter__nav {
  display: grid;
  grid-template-columns: 400px 1fr;
  gap: 1em;
}
</style>