<template> <main class="container is-fluid"> <div class="loading-content loader is-inline-flex" v-if="loading"></div> <template v-else-if="dataset"> <button class="button is-primary is-pulled-right" type="button" v-on:click="confirmationClone = hasContribAccess" :disabled="!hasContribAccess || undefined" :title="hasContribAccess ? 'Clone this dataset' : 'You need a contributor access to the project to clone this dataset'" > Clone </button> <h1 class="title is-3">Dataset {{ dataset.name }} <StateTag :state="dataset.state" /></h1> <p class="subtitle is-5 mb-2"><ItemId :item-id="dataset.id" /></p> <p v-if="corpus?.id" class="subtitle is-5"> In project <router-link :to="{ name: 'corpus-update', params: { corpusId } }"> {{ truncateShort(corpus.name) }} </router-link> </p> <hr /> <div class="field is-horizontal"> <label class="label mr-2">State</label> <StateTag :state="dataset.state" /> </div> <div class="field is-horizontal"> <label class="label mr-2">Unique elements</label> <span v-if="dataset.unique_elements">✅</span> <span v-else>❌</span> </div> <div class="field"> <label class="label">Description</label> <div class="control"> <!-- eslint-disable-next-line vue/no-v-html --> <p class="content" v-html="md.render(dataset.description)"></p> </div> </div> <div class="mt-4 tabs is-medium is-centered"> <ul> <li v-for="set in dataset.sets" :key="set.id" :class="set.id === selectedSet?.id ? 'is-active' : ''" > <a v-on:click="selectSet(set)"> {{ set.name }} <span class="tag is-rounded ml-1" :class="{ 'loader': dataset.set_elements === null }"> {{ dataset.set_elements?.[set.name] ?? '—' }} </span> </a> </li> </ul> </div> <KeepAlive v-for="set in dataset.sets" :key="set.id" > <ElementList v-if="set.id === selectedSet?.id" disabled max-size :elements="datasetSetElements(set.name)" :dataset="dataset" :set="set" > <template v-slot:no-results> <div v-if="!elementsLoading[set.name]" class="notification is-warning"> <span>No elements to display.</span> </div> <div v-else> <!-- Use a blank div to avoid the "No element to display." default notification of the ElementList component, while loading the first page. --> </div> </template> </ElementList> </KeepAlive> <div v-if="selectedSet && elementsLoading[selectedSet.name]" class="loader"></div> <button v-else-if=" selectedSet && (datasetStats[selectedSet.name] ?? Infinity) > datasetSetElements(selectedSet.name).length" class="button" v-on:click="next(selectedSet.name)" > Load more… </button> </template> <div class="notification is-danger" v-else> An error occurred loading dataset information. </div> <Modal v-model="confirmationClone" title="Clone dataset"> <p>You are about to create a copy of the dataset "{{ dataset?.name }}".</p> <p>The copy will have the same elements and attributes, except that its state will be set to <StateTag state="open" />.</p> <template v-slot:footer="{ close }"> <button class="button" v-on:click="close">Cancel</button> <button class="button is-primary ml-auto" v-on:click="clone" title="Clone the dataset" :class="{ 'is-loading': cloneLoading }" :disabled="cloneLoading || undefined" > Clone </button> </template> </Modal> </main> </template> <script lang="ts"> import MarkdownIt from 'markdown-it' import Mousetrap from 'mousetrap' import { corporaMixin, truncateMixin } from '@/mixins.js' import { mapGetters as mapVuexGetters } from 'vuex' import { mapState, mapActions } from 'pinia' import { defineComponent, PropType } from 'vue' import { UUID_REGEX } from '@/config' import { UUID } from '@/types' import Modal from '@/components/Modal.vue' import { errorParser } from '@/helpers' import { useDatasetStore, useNotificationStore } from '@/stores' import ElementList from '@/components/Navigation/ElementList.vue' import StateTag from '@/components/Corpus/Datasets/StateTag.vue' import ItemId from '@/components/ItemId.vue' import { DatasetSet } from '@/types/dataset' export default defineComponent({ mixins: [ corporaMixin, truncateMixin ], components: { ElementList, StateTag, ItemId, Modal }, props: { datasetId: { type: String as PropType<UUID>, required: true, validator: value => typeof value === 'string' && UUID_REGEX.test(value) } }, data: () => ({ selectedSet: null as (DatasetSet | null), md: new MarkdownIt({ breaks: true }), confirmationClone: false, loading: false, // Display a loader for each set elementsLoading: {} as Record<string, boolean>, cloneLoading: false }), mounted () { Mousetrap.bind('m', () => { if (this.selectedSet) this.next(this.selectedSet.name) }) }, beforeUnmount () { Mousetrap.unbind('m') }, computed: { ...mapVuexGetters('auth', ['isVerified']), ...mapState(useDatasetStore, ['datasets', 'datasetElementPagination']), dataset () { return this.datasets[this.datasetId] }, /** * Number of elements in each set. * This value is automatically computed and returned when using the API * on a single dataset (GET, POST, PATCH). It is set to null otherwise. */ datasetStats (): Record<string, number | null> { return this.dataset.set_elements || Object.fromEntries(this.dataset.sets?.map(s => [s, null]) ?? []) }, corpusId () { return this.dataset.corpus_id }, hasContribAccess () { return this.isVerified && this.corpus && this.canWrite(this.corpus) } }, methods: { ...mapActions(useDatasetStore, ['cloneDataset', 'retrieveDataset', 'nextDatasetElements']), ...mapActions(useNotificationStore, ['notify']), async get () { this.loading = true try { await this.retrieveDataset(this.datasetId) } catch (err) { this.notify({ type: 'error', text: `An error occurred retrieving dataset: ${errorParser(err)}` }) } finally { this.loading = false } }, async next (setName: string) { if (this.elementsLoading[setName]) return this.elementsLoading[setName] = true try { await this.nextDatasetElements(this.datasetId, setName) } catch (err) { this.notify({ type: 'error', text: errorParser(err) }) } finally { this.elementsLoading[setName] = false } }, selectSet (set: DatasetSet) { if (this.selectedSet?.id === set.id) return this.selectedSet = set }, async clone () { if (!this.dataset?.id || this.cloneLoading) return this.cloneLoading = true try { const data = await this.cloneDataset(this.datasetId) this.confirmationClone = false this.$router.push({ name: 'dataset-details', params: { datasetId: data.id } }) } finally { this.cloneLoading = false } }, // Elements from the current pagination. Defaults to an empty list datasetSetElements (setName: string) { return (this.datasetElementPagination[this.datasetId]?.[setName]?.datasetElements ?? []).map(({ element }) => element) } }, watch: { datasetId: { handler: 'get', immediate: true }, dataset (newValue) { this.selectSet(newValue.sets[0]) }, selectedSet: { immediate: true, handler (newValue) { if (!newValue || this.datasetElementPagination[this.datasetId]?.[newValue.name]) return this.next(newValue.name) } } } }) </script>