tethys.backend/resources/js/Pages/Submitter/Dataset/Edit.vue
Arno Kaimbacher c245c8e97d hotfix (dataset): improve dataset classification and review workflow
- Renamed "Collections" to "Classify" in dataset category views for submitters and editors to better reflect the page's purpose.
- Modified the `SectionTitleLineWithButton` component to conditionally render the cog button based on the `showCogButton` prop.
- Updated the Dataset Edit and Create views to use `textarea` instead of `text` for title and description input fields, allowing for multi-line text.
- Added authorization checks for dataset review and reject actions in the `Reviewer/DatasetController`, and passed the `can` object to the `Review` view.
- Added a "Reject" button to the dataset review page, visible only to users with the `dataset-review-reject` permission and when the dataset is in the 'approved' state.
- Improved the display of dataset information in index views by adding dark mode styling to table headers.
- Removed unused code and comments from the Dashboard.vue file.
- Removed the `show-header-icon` property from the CardBox component in the Create.vue file.
- Updated dependencies
2025-04-24 18:25:07 +02:00

909 lines
47 KiB
Vue

<template>
<LayoutAuthenticated>
<Head title="Edit dataset" />
<SectionMain>
<SectionTitleLineWithButton :icon="mdiImageText" title="Update dataset" main>
<BaseButton :route-name="stardust.route('dataset.list')" :icon="mdiArrowLeftBoldOutline" label="Back"
color="white" rounded-full small />
</SectionTitleLineWithButton>
<NotificationBar v-if="flash.message" color="success" :icon="mdiAlertBoxOutline">
{{ flash.message }}
</NotificationBar>
<FormValidationErrors v-bind:errors="errors" />
<!-- max-w-2xl max-width: 42rem; /* 672px */ -->
<!-- <div class="max-w-2xl mx-auto"> -->
<CardBox :form="true">
<!-- <FormValidationErrors v-bind:errors="errors" /> -->
<div class="mb-4">
<!-- <label for="title" class="block text-gray-700 font-bold mb-2">Title:</label>
<input
type="text"
id="title"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
v-model="form.language"
/> -->
<div class="flex flex-col md:flex-row">
<!-- (1) language field -->
<FormField label="Language *" help="required: select dataset main language"
:class="{ 'text-red-400': form.errors.language }" class="w-full flex-1">
<FormControl required v-model="form.language" :type="'select'"
placeholder="[Enter Language]" :errors="form.errors.language"
:options="{ de: 'de', en: 'en' }">
<div class="text-red-400 text-sm" v-if="form.errors.language">
{{ form.errors.language.join(', ') }}
</div>
</FormControl>
</FormField>
</div>
<!-- (2) licenses -->
<FormField label="Licenses" wrap-body :class="{ 'text-red-400': form.errors.licenses }"
class="mt-8 w-full mx-2 flex-1">
<FormCheckRadioGroup type="radio" v-model="form.licenses" name="licenses" is-column
:options="licenses" />
</FormField>
<div class="flex flex-col md:flex-row">
<!-- (3) dataset_type -->
<FormField label="Dataset Type *" help="required: dataset type"
:class="{ 'text-red-400': form.errors.type }" class="w-full mx-2 flex-1">
<FormControl required v-model="form.type" :type="'select'" placeholder="-- select type --"
:errors="form.errors.type" :options="doctypes">
<div class="text-red-400 text-sm"
v-if="form.errors.type && Array.isArray(form.errors.type)">
{{ form.errors.type.join(', ') }}
</div>
</FormControl>
</FormField>
<!-- (4) creating_corporation -->
<FormField label="Creating Corporation *"
:class="{ 'text-red-400': form.errors.creating_corporation }" class="w-full mx-2 flex-1">
<FormControl required v-model="form.creating_corporation" type="text"
placeholder="[enter creating corporation]" :is-read-only="true">
<div class="text-red-400 text-sm"
v-if="form.errors.creating_corporation && Array.isArray(form.errors.creating_corporation)">
{{ form.errors.creating_corporation.join(', ') }}
</div>
</FormControl>
</FormField>
</div>
<BaseDivider />
<!-- (5) titles -->
<CardBox class="mb-6 shadow" :has-form-data="false" title="Titles" :icon="mdiFinance"
:header-icon="mdiPlusCircle" v-on:header-icon-click="addTitle()">
<div class="flex flex-col md:flex-row">
<FormField label="Main Title *" help="required: main title"
:class="{ 'text-red-400': form.errors['titles.0.value'] }" class="w-full mr-1 flex-1">
<FormControl required v-model="form.titles[0].value" type="textarea"
placeholder="[enter main title]" :show-char-count="true" :max-input-length="255">
<div class="text-red-400 text-sm"
v-if="form.errors['titles.0.value'] && Array.isArray(form.errors['titles.0.value'])">
{{ form.errors['titles.0.value'].join(', ') }}
</div>
</FormControl>
</FormField>
<FormField label="Main Title Language*" help="required: main title language"
:class="{ 'text-red-400': form.errors['titles.0.language'] }"
class="w-full ml-1 flex-1">
<FormControl required v-model="form.titles[0].language" type="text"
:is-read-only="true">
<div class="text-red-400 text-sm"
v-if="form.errors['titles.0.language'] && Array.isArray(form.errors['titles.0.language'])">
{{ form.errors['titles.0.language'].join(', ') }}
</div>
</FormControl>
</FormField>
</div>
<label v-if="form.titles.length > 1">additional titles </label>
<!-- <BaseButton :icon="mdiPlusCircle" @click.prevent="addTitle()" color="modern" rounded-full small /> -->
<table>
<thead>
<tr>
<!-- <th v-if="checkable" /> -->
<th>Title Value</th>
<th>Title Type</th>
<th>Title Language</th>
<th />
</tr>
</thead>
<tbody>
<template v-for="(title, index) in form.titles" :key="index">
<tr v-if="title.type != 'Main'">
<!-- <td scope="row">{{ index + 1 }}</td> -->
<td data-label="Title Value">
<FormControl required v-model="form.titles[index].value" type="textarea"
placeholder="[enter main title]">
<div class="text-red-400 text-sm"
v-if="form.errors[`titles.${index}.value`]">
{{ form.errors[`titles.${index}.value`].join(', ') }}
</div>
</FormControl>
</td>
<td data-label="Title Type">
<FormControl required v-model="form.titles[index].type" type="select"
:options="titletypes" placeholder="[select title type]">
<div class="text-red-400 text-sm"
v-if="Array.isArray(form.errors[`titles.${index}.type`])">
{{ form.errors[`titles.${index}.type`].join(', ') }}
</div>
</FormControl>
</td>
<td data-label="Title Language">
<FormControl required v-model="form.titles[index].language" type="select"
:options="{ de: 'de', en: 'en' }" placeholder="[select title language]">
<div class="text-red-400 text-sm"
v-if="form.errors[`titles.${index}.language`]">
{{ form.errors[`titles.${index}.language`].join(', ') }}
</div>
</FormControl>
</td>
<td class="before:hidden lg:w-1 whitespace-nowrap">
<BaseButtons type="justify-start lg:justify-end" no-wrap>
<!-- <BaseButton color="info" :icon="mdiEye" small @click="isModalActive = true" /> -->
<BaseButton color="danger" :icon="mdiTrashCan" small
v-if="title.id == undefined" @click.prevent="removeTitle(index)" />
</BaseButtons>
</td>
</tr>
</template>
</tbody>
</table>
</CardBox>
<!-- (6) descriptions -->
<CardBox class="mb-6 shadow" :has-form-data="false" title="Descriptions" :icon="mdiFinance"
:header-icon="mdiPlusCircle" v-on:header-icon-click="addDescription()">
<div class="flex flex-col md:flex-row">
<FormField label="Main Abstract *" help="required: main abstract"
:class="{ 'text-red-400': form.errors['descriptions.0.value'] }"
class="w-full mr-1 flex-1">
<FormControl required v-model="form.descriptions[0].value" type="textarea"
placeholder="[enter main abstract]" :show-char-count="true"
:max-input-length="2500">
<div class="text-red-400 text-sm"
v-if="form.errors['descriptions.0.value'] && Array.isArray(form.errors['descriptions.0.value'])">
{{ form.errors['descriptions.0.value'].join(', ') }}
</div>
</FormControl>
</FormField>
<FormField label="Main Title Language*" help="required: main abstract language"
:class="{ 'text-red-400': form.errors['descriptions.0.language'] }"
class="w-full ml-1 flex-1">
<FormControl required v-model="form.descriptions[0].language" type="text"
:is-read-only="true">
<div class="text-red-400 text-sm" v-if="form.errors['descriptions.0.value'] && Array.isArray(form.errors['descriptions.0.language'])
">
{{ form.errors['descriptions.0.language'].join(', ') }}
</div>
</FormControl>
</FormField>
</div>
<table>
<thead>
<tr>
<!-- <th v-if="checkable" /> -->
<th>Title Value</th>
<th>Title Type</th>
<th>Title Language</th>
<th />
</tr>
</thead>
<tbody>
<template v-for="(item, index) in form.descriptions" :key="index">
<tr v-if="item.type != 'Abstract'">
<!-- <td scope="row">{{ index + 1 }}</td> -->
<td data-label="Description Value">
<FormControl required v-model="form.descriptions[index].value" type="textarea"
placeholder="[enter main title]">
<div class="text-red-400 text-sm"
v-if="form.errors[`descriptions.${index}.value`]">
{{ form.errors[`descriptions.${index}.value`].join(', ') }}
</div>
</FormControl>
</td>
<td data-label="Description Type">
<FormControl required v-model="form.descriptions[index].type" type="select"
:options="descriptiontypes" placeholder="[select title type]">
<div class="text-red-400 text-sm"
v-if="Array.isArray(form.errors[`descriptions.${index}.type`])">
{{ form.errors[`descriptions.${index}.type`].join(', ') }}
</div>
</FormControl>
</td>
<td data-label="Description Language">
<FormControl required v-model="form.descriptions[index].language"
type="select" :options="{ de: 'de', en: 'en' }"
placeholder="[select title language]">
<div class="text-red-400 text-sm"
v-if="form.errors[`descriptions.${index}.language`]">
{{ form.errors[`descriptions.${index}.language`].join(', ') }}
</div>
</FormControl>
</td>
<td class="before:hidden lg:w-1 whitespace-nowrap">
<BaseButtons type="justify-start lg:justify-end" no-wrap>
<!-- <BaseButton color="info" :icon="mdiEye" small @click="isModalActive = true" /> -->
<BaseButton color="danger" :icon="mdiTrashCan" small
v-if="item.id == undefined"
@click.prevent="removeDescription(index)" />
</BaseButtons>
</td>
</tr>
</template>
</tbody>
</table>
</CardBox>
<!-- (7) authors -->
<CardBox class="mb-6 shadow" has-table title="Creators" :icon="mdiBookOpenPageVariant"
:header-icon="mdiPlusCircle" v-on:header-icon-click="addNewAuthor()">
<SearchAutocomplete source="/api/persons" :response-property="'first_name'"
placeholder="search in person table...." v-on:person="onAddAuthor"></SearchAutocomplete>
<TablePersons :persons="form.authors" v-if="form.authors.length > 0" :errors="form.errors"
:relation="'authors'" />
<div class="text-red-400 text-sm"
v-if="form.errors.authors && Array.isArray(form.errors.authors)">
{{ form.errors.authors.join(', ') }}
</div>
</CardBox>
<!-- (8) contributors -->
<CardBox class="mb-6 shadow" has-table title="Contributors" :icon="mdiBookOpenPageVariant"
:header-icon="mdiPlusCircle" v-on:header-icon-click="addNewContributor()">
<SearchAutocomplete source="/api/persons" :response-property="'first_name'"
placeholder="search in person table...." v-on:person="onAddContributor">
</SearchAutocomplete>
<TablePersons :persons="form.contributors" v-if="form.contributors.length > 0"
:contributortypes="contributorTypes" :errors="form.errors" :relation="'contributors'" />
<div class="text-red-400 text-sm"
v-if="form.errors.contributors && Array.isArray(form.errors.contributors)">
{{ form.errors.contributors.join(', ') }}
</div>
</CardBox>
<div class="flex flex-col md:flex-row">
<!-- (9) project_id -->
<FormField label="Project.." help="project is optional"
:class="{ 'text-red-400': form.errors.project_id }" class="w-full mx-2 flex-1">
<FormControl required v-model="form.project_id" :type="'select'"
placeholder="[Select Project]" :errors="form.errors.project_id" :options="projects">
<div class="text-red-400 text-sm" v-if="form.errors.project_id">
{{ form.errors.project_id.join(', ') }}
</div>
</FormControl>
</FormField>
<!-- (10) embargo_date -->
<FormField label="Embargo Date.." help="embargo date is optional"
:class="{ 'text-red-400': form.errors.embargo_date }" class="w-full mx-2 flex-1">
<FormControl v-model="form.embargo_date" :type="'date'" placeholder="date('y-m-d')"
:errors="form.errors.embargo_date">
<div class="text-red-400 text-sm" v-if="form.errors.embargo_date">
{{ form.errors.embargo_date.join(', ') }}
</div>
</FormControl>
</FormField>
</div>
<BaseDivider />
<MapComponent v-if="form.coverage" :mapOptions="mapOptions" :baseMaps="baseMaps"
:fitBounds="fitBounds" :coverage="form.coverage" :mapId="mapId"
v-bind-event:onMapInitializedEvent="onMapInitialized">
</MapComponent>
<div class="flex flex-col md:flex-row">
<!-- x min and max -->
<FormField label="Coverage X Min" :class="{ 'text-red-400': form.errors['coverage.x_min'] }"
class="w-full mx-2 flex-1">
<FormControl required v-model="form.coverage.x_min" type="text" placeholder="[enter x_min]">
<div class="text-red-400 text-sm"
v-if="form.errors['coverage.x_min'] && Array.isArray(form.errors['coverage.x_min'])">
{{ form.errors['coverage.x_min'].join(', ') }}
</div>
</FormControl>
</FormField>
<FormField label="Coverage X Max" :class="{ 'text-red-400': form.errors['coverage.x_max'] }"
class="w-full mx-2 flex-1">
<FormControl required v-model="form.coverage.x_max" type="text" placeholder="[enter x_max]">
<div class="text-red-400 text-sm"
v-if="form.errors['coverage.x_max'] && Array.isArray(form.errors['coverage.x_max'])">
{{ form.errors['coverage.x_max'].join(', ') }}
</div>
</FormControl>
</FormField>
<!-- y min and max -->
<FormField label="Coverage Y Min" :class="{ 'text-red-400': form.errors['coverage.y_min'] }"
class="w-full mx-2 flex-1">
<FormControl required v-model="form.coverage.y_min" type="text" placeholder="[enter y_min]">
<div class="text-red-400 text-sm"
v-if="form.errors['coverage.y_min'] && Array.isArray(form.errors['coverage.y_min'])">
{{ form.errors['coverage.y_min'].join(', ') }}
</div>
</FormControl>
</FormField>
<FormField label="Coverage Y Max" :class="{ 'text-red-400': form.errors['coverage.y_max'] }"
class="w-full mx-2 flex-1">
<FormControl required v-model="form.coverage.y_max" type="text" placeholder="[enter y_max]">
<div class="text-red-400 text-sm"
v-if="form.errors['coverage.y_max'] && Array.isArray(form.errors['coverage.y_max'])">
{{ form.errors['coverage.y_max'].join(', ') }}
</div>
</FormControl>
</FormField>
</div>
<CardBox class="mb-6 shadow" has-table title="Dataset References" :icon="mdiEarthPlus"
:header-icon="mdiPlusCircle" v-on:header-icon-click="addReference">
<!-- Message when no references exist -->
<div v-if="form.references.length === 0" class="text-center py-4">
<p class="text-gray-600">No references added yet.</p>
<p class="text-gray-400">Click the plus icon above to add a new reference.</p>
</div>
<!-- Reference form -->
<table class="table-fixed border-green-900" v-if="form.references.length">
<thead>
<tr>
<th class="w-4/12">Value</th>
<th class="w-2/12">Type</th>
<th class="w-3/12">Relation</th>
<th class="w-2/12">Label</th>
<th class="w-1/12"></th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in form.references">
<td data-label="Reference Value">
<!-- <input name="Reference Value" class="form-control"
placeholder="[VALUE]" v-model="item.value" /> -->
<FormControl required v-model="item.value" :type="'text'" placeholder="[VALUE]"
:errors="form.errors.embargo_date">
<div class="text-red-400 text-sm"
v-if="form.errors[`references.${index}.value`] && Array.isArray(form.errors[`references.${index}.value`])">
{{ form.errors[`references.${index}.value`].join(', ') }}
</div>
</FormControl>
</td>
<td>
<FormControl required v-model="form.references[index].type" type="select"
:options="referenceIdentifierTypes" placeholder="[type]">
<div class="text-red-400 text-sm"
v-if="Array.isArray(form.errors[`references.${index}.type`])">
{{ form.errors[`references.${index}.type`].join(', ') }}
</div>
</FormControl>
</td>
<td>
<!-- {!! Form::select('Reference[Relation]', $relationTypes, null,
['placeholder' => '[relationType]', 'v-model' => 'item.relation',
'data-vv-scope' => 'step-2'])
!!} -->
<FormControl required v-model="form.references[index].relation" type="select"
:options="relationTypes" placeholder="[relation type]">
<div class="text-red-400 text-sm"
v-if="Array.isArray(form.errors[`references.${index}.relation`])">
{{ form.errors[`references.${index}.relation`].join(', ') }}
</div>
</FormControl>
</td>
<td data-label="Reference Label">
<!-- <input name="Reference Label" class="form-control" v-model="item.label" /> -->
<FormControl required v-model="form.references[index].label" type="text"
placeholder="[reference label]">
<div class="text-red-400 text-sm"
v-if="form.errors[`references.${index}.label`]">
{{ form.errors[`references.${index}.label`].join(', ') }}
</div>
</FormControl>
</td>
<td class="before:hidden lg:w-1 whitespace-nowrap">
<!-- <BaseButton color="info" :icon="mdiEye" small @click="isModalActive = true" /> -->
<BaseButton color="danger" :icon="mdiTrashCan" small
@click.prevent="removeReference(index)" />
</td>
</tr>
</tbody>
</table>
<!-- References to delete section -->
<div v-if="form.referencesToDelete && form.referencesToDelete.length > 0" class="mt-8">
<h1 class="pt-8 pb-3 font-semibold sm:text-lg text-gray-900">References To Delete</h1>
<ul class="flex flex-1 flex-wrap -m-1">
<li v-for="(element, index) in form.referencesToDelete" :key="index"
class="block p-1 w-1/2 sm:w-1/3 md:w-1/4 lg:w-1/6 xl:w-1/8 h-40">
<article tabindex="0"
class="bg-red-100 group w-full h-full rounded-md cursor-pointer relative shadow-sm overflow-hidden">
<section
class="flex flex-col rounded-md text-xs break-words w-full h-full z-20 absolute top-0 py-2 px-3">
<h1
class="flex-1 text-gray-700 group-hover:text-blue-800 font-medium text-sm mb-1 truncate overflow-hidden whitespace-nowrap">
{{ element.value }}
</h1>
<div class="flex flex-col mt-auto">
<p class="p-1 size text-xs text-gray-700">
<span class="font-semibold">Type:</span> {{ element.type }}
</p>
<p class="p-1 size text-xs text-gray-700">
<span class="font-semibold">Relation:</span> {{ element.relation }}
</p>
<div class="flex justify-end mt-1">
<button
class="restore ml-auto focus:outline-none hover:bg-gray-300 p-1 rounded-md text-gray-800"
@click.prevent="restoreReference(index)">
<svg viewBox="0 0 24 24" class="w-5 h-5">
<path fill="currentColor" :d="mdiRestore"></path>
</svg>
</button>
</div>
</div>
</section>
</article>
</li>
</ul>
</div>
</CardBox>
<BaseDivider />
<CardBox class="mb-6 shadow" has-table title="Dataset Keywords" :icon="mdiEarthPlus"
:header-icon="mdiPlusCircle" v-on:header-icon-click="addKeyword">
<!-- <ul>
<li v-for="(subject, index) in form.subjects" :key="index">
{{ subject.value }} <BaseButton color="danger" :icon="mdiTrashCan" small @click.prevent="removeKeyword(index)" />
</li>
</ul> -->
<TableKeywords :keywords="form.subjects" :errors="form.errors" :subjectTypes="subjectTypes"
v-model:subjects-to-delete="form.subjectsToDelete" v-if="form.subjects.length > 0" />
</CardBox>
</div>
<div class="mb-4">
<!-- <label for="project" class="block text-gray-700 font-bold mb-2">Project:</label>
<select
id="project"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
v-model="dataset.project_id"
>
<option v-for="project in projects" :key="project.id" :value="project.id" class="block px-4 py-2 text-gray-700">
{{ project.label }}
</option>
</select> -->
</div>
<FileUploadComponent v-model:files="form.files" v-model:filesToDelete="form.filesToDelete"
:showClearButton="false">
</FileUploadComponent>
<div class="text-red-400 text-sm" v-if="form.errors['file'] && Array.isArray(form.errors['files'])">
{{ form.errors['files'].join(', ') }}
</div>
<template #footer>
<BaseButtons>
<BaseButton v-if="can.edit" @click.stop="submit" :disabled="form.processing" label="Save"
color="info" :icon="mdiDisc" :class="{ 'opacity-25': form.processing }" small>
</BaseButton>
<BaseButton v-if="can.edit" :route-name="stardust.route('dataset.release', [dataset.id])"
color="info" :icon="mdiLockOpen" :label="'Release'" small
:disabled="form.processing"
:class="{ 'opacity-25': form.processing }" />
</BaseButtons>
</template>
</CardBox>
<!-- Loading Spinner -->
<div v-if="form.processing"
class="fixed inset-0 flex items-center justify-center bg-gray-500 bg-opacity-50 z-50">
<svg class="animate-spin h-12 w-12 text-white" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M12 2a10 10 0 0110 10h-4a6 6 0 00-6-6V2z"></path>
</svg>
</div>
</SectionMain>
</LayoutAuthenticated>
</template>
<script setup lang="ts">
// import EditComponent from "./../EditComponent";
// export default EditComponent;
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
import { useForm, Head, usePage } from '@inertiajs/vue3';
import { computed, ComputedRef } from 'vue';
import { Dataset, Title, Subject, TethysFile, Person, License } from '@/Dataset';
import { stardust } from '@eidellev/adonis-stardust/client';
import FormField from '@/Components/FormField.vue';
import FormControl from '@/Components/FormControl.vue';
import SectionMain from '@/Components/SectionMain.vue';
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
import FormCheckRadioGroup from '@/Components/FormCheckRadioGroup.vue';
import BaseButton from '@/Components/BaseButton.vue';
import BaseButtons from '@/Components/BaseButtons.vue';
import BaseDivider from '@/Components/BaseDivider.vue';
import CardBox from '@/Components/CardBox.vue';
import MapComponent from '@/Components/Map/map.component.vue';
import SearchAutocomplete from '@/Components/SearchAutocomplete.vue';
import TablePersons from '@/Components/TablePersons.vue';
import TableKeywords from '@/Components/TableKeywords.vue';
import FormValidationErrors from '@/Components/FormValidationErrors.vue';
import FileUploadComponent from '@/Components/FileUpload.vue';
import { MapOptions } from '@/Components/Map/MapOptions';
import { LatLngBoundsExpression } from 'leaflet';
import { LayerOptions } from '@/Components/Map/LayerOptions';
import {
mdiImageText,
mdiArrowLeftBoldOutline,
mdiPlusCircle,
mdiFinance,
mdiTrashCan,
mdiBookOpenPageVariant,
mdiEarthPlus,
mdiAlertBoxOutline,
mdiRestore,
mdiLockOpen,
mdiDisc
} from '@mdi/js';
import { notify } from '@/notiwind';
import NotificationBar from '@/Components/NotificationBar.vue';
const props = defineProps({
// errors: {
// type: Object,
// default: () => ({}),
// },
licenses: {
type: Object,
default: () => ({}),
},
languages: {
type: Object,
default: () => ({}),
},
doctypes: {
type: Object,
default: () => ({}),
},
titletypes: {
type: Object,
default: () => ({}),
},
projects: {
type: Object,
default: () => ({}),
},
descriptiontypes: {
type: Object,
default: () => ({}),
},
contributorTypes: {
type: Object,
default: () => ({}),
},
subjectTypes: {
type: Object,
default: () => ({}),
},
referenceIdentifierTypes: {
type: Object,
default: () => ({}),
},
relationTypes: {
type: Object,
default: () => ({}),
},
dataset: {
type: Object,
default: () => ({}),
},
can: {
type: Object,
default: () => ({}),
},
});
const flash: ComputedRef<any> = computed(() => {
// let test = usePage();
// console.log(test);
return usePage().props.flash;
});
const errors: ComputedRef<any> = computed(() => {
return usePage().props.errors;
});
const mapOptions: MapOptions = {
center: [48.208174, 16.373819],
zoom: 3,
zoomControl: false,
attributionControl: false,
};
const baseMaps: Map<string, LayerOptions> = new Map<string, LayerOptions>();
const fitBounds: LatLngBoundsExpression = [
[46.4318173285, 9.47996951665],
[49.0390742051, 16.9796667823],
];
const mapId = 'test';
props.dataset.filesToDelete = [];
props.dataset.subjectsToDelete = [];
props.dataset.referencesToDelete = [];
let form = useForm<Dataset>(props.dataset as Dataset);
// Add this computed property to the script section
const hasUnsavedChanges = computed(() => {
// Check if form is processing
if (form.processing) return true;
// Compare current form state with original dataset
// Check basic properties
if (form.language !== props.dataset.language) return true;
if (form.type !== props.dataset.type) return true;
if (form.project_id !== props.dataset.project_id) return true;
if (form.embargo_date !== props.dataset.embargo_date) return true;
// Check if licenses have changed
const originalLicenses = Array.isArray(props.dataset.licenses)
? props.dataset.licenses.map(l => typeof l === 'object' ? l.id.toString() : l)
: [];
const currentLicenses = Array.isArray(form.licenses)
? form.licenses.map(l => typeof l === 'object' ? l.id.toString() : l)
: [];
if (JSON.stringify(currentLicenses) !== JSON.stringify(originalLicenses)) return true;
// Check if titles have changed
if (JSON.stringify(form.titles) !== JSON.stringify(props.dataset.titles)) return true;
// Check if descriptions have changed
if (JSON.stringify(form.descriptions) !== JSON.stringify(props.dataset.descriptions)) return true;
// Check if authors have changed
if (JSON.stringify(form.authors) !== JSON.stringify(props.dataset.authors)) return true;
// Check if contributors have changed
if (JSON.stringify(form.contributors) !== JSON.stringify(props.dataset.contributors)) return true;
// Check if subjects have changed
// if (JSON.stringify(form.subjects) !== JSON.stringify(props.dataset.subjects)) return true;
let test = JSON.stringify(form.subjects);
let test2 = JSON.stringify(props.dataset.subjects);
if (test !== test2) {
return true;
}
// Check if references have changed
if (JSON.stringify(form.references) !== JSON.stringify(props.dataset.references)) return true;
// Check if coverage has changed
if (JSON.stringify(form.coverage) !== JSON.stringify(props.dataset.coverage)) return true;
// Check if files have changed
if (form.files?.length !== props.dataset.files?.length) return true;
if (form.filesToDelete?.length > 0) return true;
// Check if there are new files to upload
if (form.files?.some(file => !file.id)) return true;
// No changes detected
return false;
});
const submit = async (): Promise<void> => {
let route = stardust.route('dataset.update', [props.dataset.id]);
let licenses = form.licenses.map((obj) => {
if (hasIdAttribute(obj)) {
return obj.id.toString()
} else {
return obj;
}
});
const [fileUploads, fileInputs] = form.files?.reduce(
([fileUploads, fileInputs], obj) => {
if (!obj.id) {
// return MultipartFile for file upload
const options: FilePropertyBag = {
type: obj.type,
lastModified: obj.lastModified,
sortOrder: obj.sort_order,
};
// const file = new File([obj.blob], `${obj.label}?sortOrder=${obj.sort_order}`, options);
// const metadata = JSON.stringify({ sort_order: obj.sort_order });
// const metadataBlob = new Blob([metadata + '\n'], { type: 'application/json' });
const file = new File([obj.blob], `${obj.label}?sortorder=${obj.sort_order}`, options,);
// const file = new File([obj.blob], `${obj.label}`, options);
// fileUploads[obj.sort_order] = file;
fileUploads.push(file);
} else {
// return normal request input
fileInputs.push(obj);
}
return [fileUploads, fileInputs];
},
[[], []] as [Array<File>, Array<TethysFile>]
) as [Array<File>, Array<TethysFile>];
await form
.transform((data) => ({
...data,
licenses: licenses,
files: fileUploads,
fileInputs: fileInputs,
// files: form.files.map((obj) => {
// let file;
// if (!obj.id) {
// // return MultipartFile for file upload
// file = new File([obj.blob], obj.label, { type: obj.type, lastModified: obj.lastModified });
// } else {
// // return normal request input
// file = obj;
// }
// return file;
// }),
rights: 'true',
}))
// .put(route);
.put(route, {
onSuccess: () => {
// console.log(form.data());
// mainService.setDataset(form.data());
// formStep.value++;
// form.filesToDelete = [];
// Clear the array using splice
form.filesToDelete?.splice(0, form.filesToDelete.length);
form.subjectsToDelete?.splice(0, form.subjectsToDelete.length);
form.referencesToDelete?.splice(0, form.referencesToDelete.length);
},
});
};
const hasIdAttribute = (obj: License | number): obj is License => {
return typeof obj === 'object' && 'id' in obj;
};
const addTitle = () => {
let newTitle: Title = { value: '', language: '', type: '' };
form.titles.push(newTitle);
};
const removeTitle = (key: any) => {
form.titles.splice(key, 1);
};
const addDescription = () => {
let newDescription = { value: '', language: '', type: '' };
form.descriptions.push(newDescription);
};
const removeDescription = (key: any) => {
form.descriptions.splice(key, 1);
};
const addNewAuthor = () => {
let newAuthor = { status: false, first_name: '', last_name: '', email: '', academic_title: '', identifier_orcid: '', name_type: 'Personal' };
form.authors.push(newAuthor);
};
const onAddAuthor = (person: Person) => {
if (form.authors.filter((e) => e.id === person.id).length > 0) {
notify({ type: 'warning', title: 'Warning', text: 'person is already defined as author' }, 4000);
} else if (form.contributors.filter((e) => e.id === person.id).length > 0) {
notify({ type: 'warning', title: 'Warning', text: 'person is already defined as contributor' });
} else {
form.authors.push(person);
notify({ type: 'info', text: 'person has been successfully added as author' });
}
};
const addNewContributor = () => {
let newContributor = { status: false, first_name: '', last_name: '', email: '', academic_title: '', identifier_orcid: '', name_type: 'Personal', pivot: { contributor_type: '' } };
form.contributors.push(newContributor);
};
const onAddContributor = (person: Person) => {
if (form.contributors.filter((e) => e.id === person.id).length > 0) {
notify({ type: 'warning', title: 'Warning', text: 'person is already defined as contributor' }, 4000);
} else if (form.authors.filter((e) => e.id === person.id).length > 0) {
notify({ type: 'warning', title: 'Warning', text: 'person is already defined as author' }, 4000);
} else {
// person.pivot = { contributor_type: '' };
// // person.pivot = { name_type: '', contributor_type: '' };
form.contributors.push(person);
notify({ type: 'info', text: 'person has been successfully added as contributor' }, 4000);
}
};
const addKeyword = () => {
let newSubject: Subject = { value: '', language: '', type: 'uncontrolled' };
//this.dataset.files.push(uploadedFiles[i]);
form.subjects.push(newSubject);
};
const addReference = () => {
let newReference = { value: '', label: '', relation: '', type: '' };
//this.dataset.files.push(uploadedFiles[i]);
form.references.push(newReference);
};
const removeReference = (key: any) => {
const reference = form.references[key];
// If the reference has an ID, it exists in the database
// and should be added to referencesToDelete
if (reference.id) {
// Initialize referencesToDelete array if it doesn't exist
if (!form.referencesToDelete) {
form.referencesToDelete = [];
}
// Add to referencesToDelete
form.referencesToDelete.push(reference);
}
// Remove from form.references array
form.references.splice(key, 1);
};
const restoreReference = (index: number) => {
// Get the reference from referencesToDelete
const reference = form.referencesToDelete[index];
// Add it back to form.references
form.references.push(reference);
// Remove it from referencesToDelete
form.referencesToDelete.splice(index, 1);
};
const onMapInitialized = (newItem: any) => {
console.log(newItem);
};
</script>
<style scoped>
.max-w-2xl {
max-width: 2xl;
}
.text-2xl {
font-size: 2xl;
}
.font-bold {
font-weight: bold;
}
.mb-4 {
margin-bottom: 1rem;
}
.block {
display: block;
}
.text-gray-700 {
color: #4b5563;
}
.shadow {
box-shadow:
0 0 0 1px rgba(66, 72, 78, 0.05),
0 1px 2px 0 rgba(66, 72, 78, 0.08),
0 2px 4px 0 rgba(66, 72, 78, 0.12),
0 4px 8px 0 rgba(66, 72, 78, 0.16);
}
</style>