SpeciesFileGroup/taxonworks

View on GitHub
app/javascript/vue/tasks/leads/hub/components/KeysList.vue

Summary

Maintainability
Test Coverage
<template>
  <div>
    <VSpinner
      v-if="loading"
      legend="Loading keys..."
      :logo-size="{ width: '100px', height: '100px' }"
    />
    <h3 class="title-section">Keys</h3>
    <div class="keys_list">
      <table
        v-if="keys.length"
        class="vue-table"
      >
        <thead>
          <tr>
            <th
              @click="() => sortTable('text')"
              class="table_name_col"
            >
              Name
            </th>
            <th
              @click="() => sortTable('couplet_count')"
              class="width_shrink"
            >
              # Couplets
            </th>
            <th @click="() => sortTable('key_updated_at')">
              Last Modified
            </th>
            <th @click="() => sortTable('key_updated_by')">
              Last Modified By
            </th>
            <th class="width_shrink"/> <!-- radials -->
            <th
              @click="() => sortTable('is_public')"
              class="width_shrink"
            >
              Is Public
            </th>
          </tr>
        </thead>
        <tbody>
          <template
            v-for="(key, index) in keys"
            :key="key.id"
          >
            <tr
              class="meta_row"
              :class="{ even: (index % 2 == 0)}"
            >
              <td>
                <b>
                  <a
                    :href="RouteNames.ShowLead + '?lead_id=' + key.id"
                    target="_blank"
                  >
                    {{ key.text }}
                  </a>
                </b>
              </td>

              <td>{{ key.couplet_count }}</td>

              <td>{{ key.key_updated_at_in_words }}</td>

              <td>{{ key.key_updated_by }}</td>

              <td>
                <div class="horizontal-right-content gap-small">
                  <RadialAnnotator :global-id="key.global_id" />
                  <RadialNavigator :global-id="key.global_id" />
                </div>
              </td>

              <td>
                <input
                  type="checkbox"
                  :checked="key.is_public"
                  @click="() => changeIsPublicState(key)"
                />
              </td>
            </tr>

            <tr :class="{ even: (index % 2 == 0)}">
              <td
                colspan="2"
                class="extension_data"
              >
                <KeyOtus
                  :key-prop="key"
                  @load-otus-for-key="() => loadOtusForKey(key)"
                />
              </td>
              <td
                colspan="4"
                class="extension_data"
              >
                <KeyCitations :citations="key.citations" />
              </td>
            </tr>
          </template>
        </tbody>
      </table>

      <div v-else-if="!loading">
        No keys currently available. Use the
        <a
          :href="RouteNames.NewLead"
          data-turbolinks="false"
        >
          New dichotomous key
        </a>
        task to create one.
      </div>
    </div>
  </div>
</template>

<script setup>
import { addToArray } from '@/helpers/arrays'
import { Citation, Lead } from '@/routes/endpoints'
import { LEAD } from '@/constants/index.js'
import { onBeforeMount, ref } from 'vue'
import { RouteNames } from '@/routes/routes'
import { sortArray } from '@/helpers'
import KeyCitations from './KeyCitations.vue'
import KeyOtus from './KeyOtus.vue'
import RadialAnnotator from '@/components/radials/annotator/annotator.vue'
import RadialNavigator from '@/components/radials/navigation/radial.vue'
import VSpinner from '@/components/ui/VSpinner.vue'

const keys = ref([])
const loading = ref(true)
const ascending = ref(false)

onBeforeMount(async () => {
  const loadKeys = Lead.where({ load_root_otus: true })
    .then(({ body }) => {
      keys.value = body
    })

  const loadCitations = Citation.where({
    citation_object_type: LEAD,
    extend: ['source']
  })

  Promise.allSettled([loadKeys, loadCitations])
    .then(([_, { value }]) => {
      addCitationsToKeysList(value.body)
    })
    .catch(() => {})
    .finally(() => {
      loading.value = false
    })
})

function addCitationsToKeysList(citations) {
  citations.forEach((citation) => {
    const i = keys.value.findIndex(
      (key) => (key.id == citation.citation_object_id)
    )
    if (i != -1) {
      if (keys.value[i].citations) {
        keys.value[i].citations.push(citation)
      } else {
        keys.value[i].citations = [ citation ]
      }
    }
  })
}

function sortTable(sortProperty) {
  keys.value = sortArray(keys.value, sortProperty, ascending.value)
  ascending.value = !ascending.value
}

function changeIsPublicState(key) {
  const payload = {
    lead: {
      is_public: !key.is_public
    },
    extend: ['updater', 'updated_at_in_words']
  }

  Lead.update_meta(key.id, payload)
    .then(({ body }) => {
      const updatedKey = {
        ...body.lead,
        otu: key.otu,
        otus_count: key.otus_count,
        couplet_count: key.couplet_count,
        citations: key.citations,
        child_otus: key.child_otus,
        key_updated_at: body.lead.updated_at,
        key_updated_at_in_words: body.lead.updated_at_in_words,
        key_updated_by: body.lead.updated_by,
        key_updated_by_id: body.lead.updated_by_id
      }

      addToArray(keys.value, updatedKey)
    })
    .catch(() => {})
}

function loadOtusForKey(key) {
  Lead.otus(key.id)
    .then(({ body }) => {
      let otus = body
      if (key.otu) {
        // Remove the root otu, which is already displayed.
        const i = otus.find((otu) => (otu.id == key.otu_id))
        if (i != -1) {
          otus.splice(i, 1)
        }
      }

      otus.sort((a, b) => {
        if (a.object_label == b.object_label) {
          return a.id < b.id ? -1 : 1
        }
        return a.object_label < b.object_label ? -1 : 1
      })

      key.child_otus = otus
      addToArray(keys.value, key)
    })
    .catch(() => {
      // Add child_otus or the loading spinner will never disappear.
      key.child_otus = []
      addToArray(keys.value, key)
    })
}
</script>

<style lang="scss" scoped>
.keys_list {
  margin-right: 1em;
  margin-bottom: 2em;
}
.width_shrink {
  width: 1%;
}
.meta_row:not(:first-child) {
  border-top: 2px solid var(--color-primary);
}
.extension_data {
  border-top: 4px dotted #eee;
  padding-top: .5em;
  padding-bottom: .5em;
}
.table_name_col {
  width: 45%;
}
</style>