Skip to content
Snippets Groups Projects
DetailsPanel.vue 8.48 KiB
<template>
  <div>
    <div v-if="!element || !corpusId" class="loading-content loader"></div>
    <template v-else>
      <template v-if="elementType.folder === false">
        <OrientationPanel :element-id="elementId" />
        <hr />
      </template>

      <template v-if="(canWriteElement(elementId) && hasMlClasses[corpusId]) || classifications.length">
        <DropdownContent id="classifications" title="Classifications">
          <Classifications v-if="element.classifications" :element="element" />
          <form v-on:submit.prevent="createClassification">
            <div v-if="canWriteElement(elementId) && hasMlClasses" class="field">
              <p class="control is-expanded">
                <MLClassSelect
                  ref="newClassificationSelect"
                  v-model="selectedNewClassification"
                  v-model:is-valid="validClassification"
                  placeholder="Add a classification"
                  exclude-manual
                  auto-select
                  :classifications="element.classifications"
                  :corpus-id="corpusId"
                />
              </p>
            </div>
            <p class="help is-danger" v-if="manualClassificationExists">A manual classification for this ML class already exists.</p>
          </form>
        </DropdownContent>
        <hr />
      </template>

      <template v-if="elementType.folder === false">
        <DropdownContent id="transcriptions" title="Transcriptions">
          <Transcriptions :element="element" />
          <template v-if="canWriteElement(elementId)">
            <div class="has-text-right">
              <a v-on:click="transcriptionModal = true">
                <button class="button has-text">
                  manage
                </button>
              </a>
            </div>
            <TranscriptionsModal
              v-if="transcriptionModal"
              v-model:modal="transcriptionModal"
              :element="element"
            />
          </template>
        </DropdownContent>
        <hr />
        <template v-if="canWriteElement(elementId)">
          <DropdownContent id="new-transcription" title="New transcription">
            <TranscriptionCreationForm :element="element" />
          </DropdownContent>
          <hr />
        </template>
      </template>

      <DropdownContent id="metadata" title="Metadata">
        <ElementMetadata :corpus-id="corpusId" :element-id="element.id" />
      </DropdownContent>
      <hr />

      <DropdownContent id="datasets" title="Datasets">
        <div class="loader m-auto is-size-3" v-if="datasets === null">
        </div>
        <div class="message-body has-text-grey" v-else-if="datasets.length === 0">
          No datasets
        </div>
        <table v-else class="table is-fullwidth is-hoverable">
          <thead>
            <th>Name</th>
            <th>Set</th>
          </thead>
          <tbody>
            <tr v-for="({ dataset, set }) in datasets" :key="dataset.id + set">
              <td>
                <router-link
                  :to="{ name: 'dataset-details', params: { datasetId: dataset.id } }"
                  title="Dataset details"
                >
                  {{ dataset.name }}
                </router-link>
              </td>
              <td>{{ set }}</td>
            </tr>
          </tbody>
        </table>
      </DropdownContent>

      <EntityLinks
        :element-id="element.id"
        v-if="elementType.folder === false"
      />
    </template>
  </div>
</template>

<script lang="ts">
import {
  mapState as mapVuexState,
  mapGetters as mapVuexGetters,
  mapActions as mapVuexActions
} from 'vuex'
import { corporaMixin } from '@/mixins'
import { defineComponent, PropType } from 'vue'
import { UUID_REGEX } from '@/config'
import { UUID, Element } from '@/types'
import { ElementDataset } from '@/types/dataset'
import { mapState, mapActions } from 'pinia'
import { useElementsStore } from '@/stores'

import DropdownContent from '@/components/DropdownContent.vue'
import MLClassSelect from '@/components/MLClassSelect.vue'
import EntityLinks from '@/components/Entity/Links.vue'
import Classifications from '@/components/Element/Classifications'
import ElementMetadata from '@/components/Element/Metadata'
import OrientationPanel from '@/components/Element/OrientationPanel.vue'
import Transcriptions from '@/components/Element/Transcription'
import TranscriptionsModal from '@/components/Element/Transcription/Modal.vue'
import TranscriptionCreationForm from '@/components/Element/Transcription/CreationForm.vue'

export default defineComponent({
  mixins: [
    corporaMixin
  ],
  components: {
    Classifications,
    DropdownContent,
    ElementMetadata,
    EntityLinks,
    MLClassSelect,
    OrientationPanel,
    TranscriptionCreationForm,
    Transcriptions,
    TranscriptionsModal
  },
  props: {
    /**
     * Id of the element
     */
    elementId: {
      type: String as PropType<UUID>,
      validator: value => typeof value === 'string' && UUID_REGEX.test(value),
      required: true
    }
  },
  data: () => ({
    selectedNewClassification: '',
    validClassification: null,
    isSavingNewClassification: false,
    transcriptionModal: false
  }),
  computed: {
    ...mapVuexState('elements', ['elements', 'transcriptions']),
    ...mapVuexState('process', ['workerVersions', 'workers']),
    ...mapVuexState('classification', ['hasMlClasses']),
    ...mapState(useElementsStore, ['elementDatasets']),
    ...mapVuexGetters('elements', {
      // canWrite and canAdmin are already defined in corporaMixin
      canWriteElement: 'canWrite',
      canAdminElement: 'canAdmin'
    }),
    element (): Element | null {
      return this.elements?.[this.elementId] ?? null
    },
    corpusId (): UUID | null {
      return this.element?.corpus?.id ?? null
    },
    elementType () {
      return this.element ? this.getType(this.element.type) : {}
    },
    manualClassificationExists () {
      // A manual classification with the selected ML class exists; cannot create
      return this.selectedNewClassification &&
        this.element &&
        this.element.classifications &&
        this.element.classifications.find(c => (c.ml_class.id === this.selectedNewClassification && !c.worker_version && !c.worker_run))
    },
    canCreateClassification () {
      return this.element && this.selectedNewClassification && !this.manualClassificationExists && !this.isSavingNewClassification
    },
    classifications () {
      return (this.element && this.element.classifications) || []
    },
    metadata () {
      // @ts-expect-error Some Element attributes like metadata are set on the fly on the former store
      return (this.element && this.element?.metadata) || []
    },
    datasets (): ElementDataset[] | null {
      return this.elementDatasets?.[this.elementId] ?? null
    }
  },
  methods: {
    ...mapVuexActions('classification', { classificationCreate: 'create' }),
    ...mapVuexActions('elements', { retrieveElement: 'get' }),
    ...mapActions(useElementsStore, ['listElementDatasets']),
    async createClassification () {
      if (!this.canCreateClassification) return
      this.isSavingNewClassification = true
      try {
        await this.classificationCreate({
          elementId: this.elementId,
          mlClass: this.selectedNewClassification
        })
      } finally {
        this.selectedNewClassification = ''
        this.isSavingNewClassification = false
      }
    }
  },
  watch: {
    elementId: {
      immediate: true,
      handler (id: UUID) {
        if (!id) return
        /*
         * Do not retrieve the element again if it already exists in the store,
         * unless it lacks some of the attributes only available from RetrieveElement.
         * Some elements in the store can come from list endpoints such as those of the children tree.
         * This ensures there are no strange behaviors where some actions are only sometimes disabled when they shouldn't,
         * or some element attributes are not displayed at all.
         */
        if (!this.element || this.element.id !== id || !this.element.rights || !this.element.classifications) this.retrieveElement({ id })
        if (!Array.isArray(this.elementDatasets[id])) this.listElementDatasets(id)
      }
    },
    selectedNewClassification () {
      this.createClassification()
    }
  }
})
</script>

<style scoped>
.loading-content {
  font-size: 2.5rem;
  margin: 2.5rem auto 0 auto;
}
.button.has-tooltip-multiline {
  width: 1rem;
  height: 1.5rem;
  margin-right: 1ch;
}
.has-margin-left {
  margin-left: auto;
}
</style>