SpeciesFileGroup/taxonworks

View on GitHub
app/javascript/vue/tasks/leads/new_lead/components/Couplet.vue

Summary

Maintainability
Test Coverage
<template>
  <VSpinner
    v-if="loading"
    full-screen
  />

  <div class="cplt_center">
    <VBtn
      :disabled="!store.lead.parent_id"
      color="primary"
      medium
      @click="previousCouplet"
    >
      Go to the previous couplet
    </VBtn>
  </div>

  <div v-if="hasChildren">
    <div class="cplt_center">
      <label>Couplet number from citation: </label>
      <input
        v-model="store.lead.origin_label"
        type="text"
        class="normal-input"
        size="3"
      />
    </div>
    <div class="cplt_horizontal_buttons">
      <VBtn
        color="update"
        medium
        @click="updateCouplet"
      >
        Update
      </VBtn>

      <VBtn
        v-if="allowDestroyCouplet"
        color="destroy"
        medium
        @click="destroyCouplet"
      >
        Delete both sides
      </VBtn>

      <VBtn
        v-else-if="allowDeleteCouplet"
        color="destroy"
        medium
        @click="deleteCouplet"
      >
        Delete both sides and reparent children from
        {{ leftHasChildren ? 'left' : 'right' }}
      </VBtn>

      <VBtn
        v-else
        color="destroy"
        disabled
        medium
      >
        Can't delete when both sides have children
      </VBtn>
    </div>

    <div class="left_and_right_cplt">
      <Lead
        side="left"
        :redirect-options="redirectOptions"
        :side-has-children="leftHasChildren"
        @editing-has-occurred="() => emit('editingHasOccurred')"
      />
      <Lead
        side="right"
        :redirect-options="redirectOptions"
        :side-has-children="rightHasChildren"
        @editing-has-occurred="() => emit('editingHasOccurred')"
      />
    </div>
  </div>

  <div v-else class="cplt_center">
    <p>No couplets below this one.</p>
    <VBtn
      color="update"
      medium
      @click="nextCouplet"
    >
      Create the next couplet
    </VBtn>
  </div>
</template>

<script setup>
import Lead from './Lead.vue'
import VBtn from '@/components/ui/VBtn/index.vue'
import VSpinner from '@/components/ui/VSpinner.vue'
import { computed, ref, watch } from 'vue'
import { Lead as LeadEndpoint } from '@/routes/endpoints'
import { useStore } from '../store/useStore.js'

const emit = defineEmits(['editingHasOccurred'])

const store = useStore()

const loading = ref(false)

const hasChildren = computed(() => {
  return store.left && !!store.left.id && store.right && !!store.right.id
})

const leftHasChildren = computed(() => {
  return store.left && !!store.left.id &&
    // Ignore redirect children.
    !store.left.redirect_id &&
    store.left_future.length > 0
})

const rightHasChildren = computed(() => {
  return store.right && !!store.right.id &&
    // Ignore redirect children.
    !store.right.redirect_id &&
    store.right_future.length > 0
})

const allowDestroyCouplet = computed(() => {
  return (
    (!leftHasChildren.value && !rightHasChildren.value) &&
    !!store.lead.parent_id
  )
})

const allowDeleteCouplet = computed(() => {
  return (
    (!leftHasChildren.value && rightHasChildren.value) ||
    (!rightHasChildren.value && leftHasChildren.value)
  )
})

const redirectOptions = ref([])

function loadRedirectOptions() {
  LeadEndpoint.all_texts(store.lead.id).then(({ body }) => {
    const texts = body.all_texts.flatMap((o) => {
      if (
        (store.left && o.id == store.left.id) ||
        (store.right && o.id == store.right.id)
      ) {
        return []
      }
      let id_label = o.label ? ('[' + o.label + ']') : ''
      id_label = id_label + '[tw:' + o.id + ']'
      return [{
        id: o.id,
        text: id_label + ' ' + (o.text || '(No text)')
      }]
    })
    redirectOptions.value = texts
  })
}

watch(
  () => store.lead.id,
  () => loadRedirectOptions(),
  { immediate: true }
)

function previousCouplet() {
  if (store.dataChangedSinceLastSave() &&
    !window.confirm(
      'You have unsaved data, are you sure you want to navigate to a new couplet?'
    )
  ) {
    return
  }
  store.loadKey(store.lead.parent_id)
  emit('editingHasOccurred')
}

function updateCouplet() {
  const payload = {
      lead: store.lead,
      left: store.left,
      right: store.right
  }

  loading.value = true
  LeadEndpoint.update(store.lead.id, payload)
    .then(({ body }) => {
      // Future changes when redirect changes, so reload.
      store.loadKey(body)
      TW.workbench.alert.create('Couplet was successfully saved.', 'notice')
      emit('editingHasOccurred')
    })
    .catch(() => {})
    .finally(() => {
      loading.value = false
    })
}

function nextCouplet() {
  loading.value = true
  LeadEndpoint.create_for_edit(store.lead.id)
    .then(({ body }) => {
      store.loadKey(body)
      emit('editingHasOccurred')
    })
    .finally(() => {
      loading.value = false
    })
}

function destroyCouplet() {
  if (window.confirm(
    'Delete both left and right sides?'
  )) {
    loading.value = true
    LeadEndpoint.destroy_couplet(store.lead.id)
      .then(() => {
        store.loadKey(store.lead.id)
        TW.workbench.alert.create('Couplet was successfully deleted.', 'notice')
        emit('editingHasOccurred')
      })
      .catch(() => {})
      .finally(() => {
        loading.value = false
      })
  }
}

function deleteCouplet() {
  if (window.confirm(
    'Delete both left and right sides and attach orphaned children to this parent?'
  )) {
    loading.value = true
    LeadEndpoint.delete_couplet(store.lead.id)
      .then(() => {
        store.loadKey(store.lead.id)
        TW.workbench.alert.create('Couplet was successfully deleted.', 'notice')
        emit('editingHasOccurred')
      })
      .catch(() => {})
      .finally(() => {
        loading.value = false
      })
  }
}
</script>

<style lang="scss" scoped>
.left_and_right_cplt {
  display: flex;
  justify-content:space-around;
}
.cplt_center {
  margin-top: 1em;
  margin-bottom: 1em;
  text-align: center;
}
.cplt_horizontal_buttons {
  display: grid;
  grid-template-columns: 50% 50%;
  column-gap: 0px;
  justify-items: start;
  margin-bottom: 2em;
  margin-top: 2em;
  :first-child {
    justify-self: end;
    margin-right: 2em;
  }
  :last-child {
    margin-left: 2em;
  }
}
.title_and_annotator {
  margin-top: 1em;
  span {
    display: inline-block;
  }
  // The radial annotator icon
  div {
    vertical-align: 6px;
    display: inline-block;
    margin-left: .5em;
  }
}
</style>