hotfix: enhance editor dataset management and UI improvements
- Implemented dataset editing functionality for editor roles, including fetching, updating, and categorizing datasets. - Added routes and controller actions for editing, updating, and categorizing datasets within the editor interface. - Integrated UI components for managing dataset metadata, subjects, references, and files. - Enhanced keyword management with features for adding, editing, and deleting keywords, including handling keywords used by multiple datasets. - Improved reference management with features for adding, editing, and deleting dataset references. - Added validation for dataset updates using the `updateEditorDatasetValidator`. - Updated the dataset edit form to include components for managing titles, descriptions, authors, contributors, licenses, coverage, subjects, references, and files. - Implemented transaction management for dataset updates to ensure data consistency. - Added a download route for files associated with datasets. - Improved the UI for displaying and interacting with datasets in the editor index view, including adding edit and categorize buttons. - Fixed an issue where the file size was not correctly calculated. - Added a tooltip to the keyword value column in the TableKeywords component to explain the editability of keywords. - Added a section to display keywords that are marked for deletion. - Added a section to display references that are marked for deletion. - Added a restore button to the references to delete section to restore references. - Updated the SearchCategoryAutocomplete component to support read-only mode. - Updated the FormControl component to support read-only mode. - Added icons and styling improvements to various components. - Added a default value for subjectsToDelete and referencesToDelete in the dataset model. - Updated the FooterBar component to use the JustboilLogo component. - Updated the app.ts file to fetch chart data without a year parameter. - Updated the Login.vue file to invert the logo in dark mode. - Updated the AccountInfo.vue file to add a Head component.
This commit is contained in:
parent
10d159a57a
commit
f04c1f6327
30 changed files with 2284 additions and 539 deletions
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
// import { Head, Link, useForm } from '@inertiajs/inertia-vue3';
|
||||
import { ref } from 'vue';
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { useForm, Head } from '@inertiajs/vue3';
|
||||
// import { ref } from 'vue';
|
||||
// import { reactive } from 'vue';
|
||||
import {
|
||||
|
@ -126,6 +126,7 @@ const flash: Ref<any> = computed(() => {
|
|||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<Head title="Profile Security"></Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton :icon="mdiAccount" title="Profile" main>
|
||||
<BaseButton :route-name="stardust.route('dashboard')" :icon="mdiArrowLeftBoldOutline" label="Back"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<!-- <SectionFullScreen v-slot="{ cardClass }" :bg="'greenBlue'"> -->
|
||||
<SectionFullScreen v-slot="{ cardClass }">
|
||||
<a class="text-2xl font-semibold flex justify-center items-center mb-8 lg:mb-10">
|
||||
<img src="../../logo.svg" class="h-10 mr-4" alt="Windster Logo" />
|
||||
<img src="../../logo.svg" class="h-10 mr-4 dark:invert" alt="Windster Logo" />
|
||||
<!-- <span class="self-center text-2xl font-bold whitespace-nowrap">Tethys</span> -->
|
||||
</a>
|
||||
|
||||
|
|
371
resources/js/Pages/Editor/Dataset/Category.vue
Normal file
371
resources/js/Pages/Editor/Dataset/Category.vue
Normal file
|
@ -0,0 +1,371 @@
|
|||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<Head title="Collections"></Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton :icon="mdiLibraryShelves" title="Library Classification" main>
|
||||
<div class="bg-lime-100 shadow rounded-lg p-6 mb-6 flex items-center justify-between">
|
||||
<div>
|
||||
<label for="role-select" class="block text-lg font-medium text-gray-700 mb-1">
|
||||
Select Classification Role <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select id="role-select" v-model="selectedCollectionRole"
|
||||
class="w-full border border-gray-300 rounded-md p-2 text-gray-700 focus:ring-2 focus:ring-indigo-500"
|
||||
required>
|
||||
<!-- <option value="" disabled selected>Please select a role</option> -->
|
||||
<option v-for="collRole in collectionRoles" :key="collRole.id" :value="collRole">
|
||||
{{ collRole.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ml-4 hidden md:block">
|
||||
<span class="text-sm text-gray-600 italic">* required</span>
|
||||
</div>
|
||||
</div>
|
||||
</SectionTitleLineWithButton>
|
||||
|
||||
|
||||
<!-- Available TopLevel Collections -->
|
||||
<CardBox class="mb-4 rounded-lg p-4">
|
||||
<h2 class="text-lg font-bold text-gray-800 dark:text-slate-400 mb-2">Available Toplevel-Collections
|
||||
<span v-if="selectedCollectionRole && !selectedToplevelCollection"
|
||||
class="text-sm text-red-500 italic">(click to
|
||||
select)</span>
|
||||
</h2>
|
||||
<ul class="flex flex-wrap gap-2">
|
||||
<li v-for="col in collections" :key="col.id" :class="{
|
||||
'cursor-pointer p-2 border border-gray-200 rounded hover:bg-sky-50 text-sky-700 text-sm': true,
|
||||
'bg-sky-100 border-sky-500': selectedToplevelCollection && selectedToplevelCollection.id === col.id
|
||||
}" @click="onToplevelCollectionSelected(col)">
|
||||
{{ `${col.name} (${col.number})` }}
|
||||
</li>
|
||||
<li v-if="collections.length === 0" class="text-gray-800 dark:text-slate-400">
|
||||
No collections available.
|
||||
</li>
|
||||
</ul>
|
||||
</CardBox>
|
||||
|
||||
<!-- Collections Listing -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
||||
|
||||
<!-- Broader Collection (Parent) -->
|
||||
<CardBox v-if="selectedCollection" class="rounded-lg p-4" has-form-data>
|
||||
<h2 class="text-lg font-bold text-gray-800 dark:text-slate-400 mb-2">Broader Collection</h2>
|
||||
<draggable v-if="broaderCollections.length > 0" v-model="broaderCollections"
|
||||
:group="{ name: 'collections' }" tag="ul" class="flex flex-wrap gap-2 max-h-60 overflow-y-auto">
|
||||
<template #item="{ element: parent }">
|
||||
<li :key="parent.id" :draggable="!parent.inUse" :class="getChildClasses(parent)"
|
||||
@click="onCollectionSelected(parent)">
|
||||
{{ `${parent.name} (${parent.number})` }}
|
||||
</li>
|
||||
</template>
|
||||
</draggable>
|
||||
<ul v-else class="flex flex-wrap gap-2 max-h-60 overflow-y-auto">
|
||||
<li class="text-gray-500 text-sm">
|
||||
No broader collections available.
|
||||
</li>
|
||||
</ul>
|
||||
</CardBox>
|
||||
|
||||
<!-- Selected Collection Details -->
|
||||
<CardBox v-if="selectedCollection" class="rounded-lg p-4" has-form-data>
|
||||
<h3 class="text-xl font-bold text-gray-800 dark:text-slate-400 mb-2">Selected Collection</h3>
|
||||
<!-- <p :class="[
|
||||
'cursor-pointer p-2 border border-gray-200 rounded text-sm',
|
||||
selectedCollection.inUse ? 'bg-gray-200 text-gray-500 drag-none' : 'bg-green-50 text-green-700 hover:bg-green-100 hover:underline cursor-move'
|
||||
]"></p> -->
|
||||
<draggable v-model="selectedCollectionArray" :group="{ name: 'collections', pull: 'clone', put: false }" tag="ul" class="flex flex-wrap gap-2 max-h-60 overflow-y-auto">
|
||||
<template #item="{ element }">
|
||||
<li :key="element.id" :class="[
|
||||
'p-2 border border-gray-200 rounded text-sm',
|
||||
element.inUse ? 'bg-gray-200 text-gray-500 drag-none' : 'bg-green-50 text-green-700 hover:bg-green-100 hover:underline cursor-move'
|
||||
]">
|
||||
{{ `${element.name} (${element.number})` }}
|
||||
</li>
|
||||
</template>
|
||||
</draggable>
|
||||
</CardBox>
|
||||
<!-- Narrower Collections (Children) -->
|
||||
<CardBox v-if="selectedCollection" class="rounded-lg p-4" has-form-data>
|
||||
<h2 class="text-lg font-bold text-gray-800 dark:text-slate-400 mb-2">Narrower Collections</h2>
|
||||
<draggable v-if="narrowerCollections.length > 0" v-model="narrowerCollections"
|
||||
:group="{ name: 'collections' }" tag="ul" class="flex flex-wrap gap-2 max-h-60 overflow-y-auto">
|
||||
<template #item="{ element: child }">
|
||||
<li :key="child.id" :draggable="!child.inUse" :class="getChildClasses(child)"
|
||||
@click="onCollectionSelected(child)">
|
||||
{{ `${child.name} (${child.number})` }}
|
||||
</li>
|
||||
</template>
|
||||
</draggable>
|
||||
<ul v-else class="flex flex-wrap gap-2 max-h-60 overflow-y-auto">
|
||||
<li class="text-gray-500 text-sm">
|
||||
No sub-collections available.
|
||||
</li>
|
||||
</ul>
|
||||
</CardBox>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mb-4 rounded-lg">
|
||||
<div v-if="selectedCollection || selectedCollectionList.length > 0" class="bg-gray-100 shadow rounded-lg p-6 mb-6" :class="{ 'opacity-50': selectedCollection && selectedCollectionList.length === 0 }">
|
||||
<p class="mb-4 text-gray-700">Please drag your collections here to classify your previously created
|
||||
dataset
|
||||
according to library classification standards.</p>
|
||||
<draggable v-model="selectedCollectionList" :group="{ name: 'collections' }"
|
||||
class="min-h-36 border-dashed border-2 border-gray-400 p-4 text-sm flex flex-wrap gap-2 max-h-60 overflow-y-auto"
|
||||
tag="ul"
|
||||
:disabled="selectedCollection === null && selectedCollectionList.length > 0"
|
||||
:style="{ opacity: (selectedCollection === null && selectedCollectionList.length > 0) ? 0.5 : 1, pointerEvents: (selectedCollection === null && selectedCollectionList.length > 0) ? 'none' : 'auto' }">
|
||||
<template #item="{ element }">
|
||||
<div :key="element.id"
|
||||
class="p-2 m-1 bg-sky-200 text-sky-800 rounded flex items-center gap-2 h-7">
|
||||
<span>{{ element.name }} ({{ element.number }})</span>
|
||||
<button
|
||||
@click="selectedCollectionList = selectedCollectionList.filter(item => item.id !== element.id)"
|
||||
class="hover:text-sky-600 flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6 border-t border-gray-100 dark:border-slate-800">
|
||||
<BaseButtons>
|
||||
<BaseButton @click.stop="syncDatasetCollections" label="Save" color="info" small
|
||||
:disabled="isSaveDisabled" :style="{ opacity: isSaveDisabled ? 0.5 : 1 }">
|
||||
</BaseButton>
|
||||
</BaseButtons>
|
||||
</div>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, Ref, watch, computed } from 'vue';
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
|
||||
import axios from 'axios';
|
||||
import { mdiLibraryShelves } from '@mdi/js';
|
||||
import draggable from 'vuedraggable';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import BaseButtons from '@/Components/BaseButtons.vue';
|
||||
import CardBox from '@/Components/CardBox.vue';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
import { CollectionRole, Collection } from '@/types/models';
|
||||
|
||||
// import CollectionRoleSelector from '@/Components/Collection/CollectionRoleSelector.vue';
|
||||
// import ToplevelCollections from '@/Components/Collection/ToplevelCollections.vue';
|
||||
// import CollectionHierarchy from '@/Components/Collection/CollectionHierarchy.vue';
|
||||
// import CollectionDropZone from '@/Components/Collection/CollectionDropZone.vue';
|
||||
|
||||
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
Props & Reactive State
|
||||
-------------------------------------------------------------------------- */
|
||||
const props = defineProps({
|
||||
collectionRoles: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
dataset: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
relatedCollections: {
|
||||
type: Array as () => Collection[],
|
||||
default: () => [] as const
|
||||
}
|
||||
});
|
||||
|
||||
const collectionRoles: Ref<CollectionRole[]> = ref(props.collectionRoles as CollectionRole[]);
|
||||
const collections: Ref<Collection[]> = ref<Collection[]>([]);
|
||||
const selectedCollectionRole = ref<CollectionRole | null>(null);
|
||||
const selectedToplevelCollection = ref<Collection | null>(null);
|
||||
const selectedCollection = ref<Collection | null>(null);
|
||||
const narrowerCollections = ref<Collection[]>([]);
|
||||
const broaderCollections = ref<Collection[]>([]);
|
||||
// Reactive list that holds collections dropped by the user
|
||||
const selectedCollectionList: Ref<Collection[]> = ref<Collection[]>([]);
|
||||
|
||||
|
||||
// Wrap selectedCollection in an array for draggable (always expects an array)
|
||||
const selectedCollectionArray = computed({
|
||||
get: () => (selectedCollection.value ? [selectedCollection.value] : []),
|
||||
set: (value: Collection[]) => {
|
||||
selectedCollection.value = value.length ? value[0] : null
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const form = useForm({
|
||||
collections: [] as number[],
|
||||
});
|
||||
|
||||
// Watch for changes in dropCollections
|
||||
watch(
|
||||
() => selectedCollectionList.value,
|
||||
() => {
|
||||
if (selectedCollection.value) {
|
||||
fetchCollections(selectedCollection.value.id);
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
Watchers and Initial Setup
|
||||
-------------------------------------------------------------------------- */
|
||||
// If the collectionRoles prop might load asynchronously (or change), you can watch for it:
|
||||
watch(
|
||||
() => props.collectionRoles as CollectionRole[],
|
||||
(newCollectionRoles: CollectionRole[]) => {
|
||||
collectionRoles.value = newCollectionRoles;
|
||||
// Preselect the role with name "ccs" if it exists
|
||||
const found: CollectionRole | undefined = collectionRoles.value.find(
|
||||
role => role.name.toLowerCase() === 'ccs'
|
||||
);
|
||||
if (found?.name === 'ccs') {
|
||||
selectedCollectionRole.value = found;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// When collection role changes, update available collections and clear dependent state.
|
||||
watch(
|
||||
() => selectedCollectionRole.value as CollectionRole,
|
||||
(newSelectedCollectionRole: CollectionRole | null) => {
|
||||
if (newSelectedCollectionRole != null) {
|
||||
collections.value = newSelectedCollectionRole.collections || []
|
||||
} else {
|
||||
selectedToplevelCollection.value = null;
|
||||
selectedCollection.value = null;
|
||||
collections.value = []
|
||||
}
|
||||
// Reset dependent variables when the role changes
|
||||
selectedCollection.value = null
|
||||
narrowerCollections.value = []
|
||||
broaderCollections.value = []
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
Methods
|
||||
-------------------------------------------------------------------------- */
|
||||
const onToplevelCollectionSelected = (collection: Collection) => {
|
||||
selectedToplevelCollection.value = collection;
|
||||
selectedCollection.value = collection;
|
||||
// call the API endpoint to get both.
|
||||
fetchCollections(collection.id);
|
||||
};
|
||||
|
||||
const onCollectionSelected = (collection: Collection) => {
|
||||
selectedCollection.value = collection;
|
||||
// call the API endpoint to get both.
|
||||
fetchCollections(collection.id);
|
||||
};
|
||||
|
||||
/**
|
||||
* fetchCollections: Retrieves broader and narrower collections.
|
||||
* Marks any narrower collection as inUse if it appears in selectedCollectionList.
|
||||
*/
|
||||
const fetchCollections = async (collectionId: number) => {
|
||||
try {
|
||||
const response = await axios.get(`/api/collections/${collectionId}`);
|
||||
const data = response.data;
|
||||
// Map each returned narrower collection
|
||||
narrowerCollections.value = data.narrowerCollections.map((collection: Collection) => {
|
||||
// If found, mark it as inUse.
|
||||
const alreadyDropped = selectedCollectionList.value.find(dc => dc.id === collection.id);
|
||||
return alreadyDropped ? { ...collection, inUse: true } : { ...collection, inUse: false };
|
||||
});
|
||||
broaderCollections.value = data.broaderCollection.map((collection: Collection) => {
|
||||
const alreadyDropped = selectedCollectionList.value.find(dc => dc.id === collection.id);
|
||||
return alreadyDropped ? { ...collection, inUse: true } : { ...collection, inUse: false };
|
||||
});
|
||||
// Check if selected collection is in the selected list
|
||||
if (selectedCollection.value && selectedCollectionList.value.find(dc => dc.id === selectedCollection.value.id)) {
|
||||
selectedCollection.value = { ...selectedCollection.value, inUse: true };
|
||||
} else if (selectedCollection.value) {
|
||||
selectedCollection.value = { ...selectedCollection.value, inUse: false };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in fetchCollections:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const syncDatasetCollections = async () => {
|
||||
// Extract the ids from the dropCollections list
|
||||
form.collections = selectedCollectionList.value.map((item: Collection) => item.id);
|
||||
form.put(stardust.route('editor.dataset.categorizeUpdate', [props.dataset.id]), {
|
||||
preserveState: true,
|
||||
onSuccess: () => {
|
||||
console.log('Dataset collections synced successfully');
|
||||
},
|
||||
onError: (errors) => {
|
||||
console.error('Error syncing dataset collections:', errors);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* getChildClasses returns the Tailwind CSS classes to apply to each collection list item.
|
||||
*/
|
||||
const getChildClasses = (child: Collection) => {
|
||||
return child.inUse
|
||||
? 'p-2 border border-gray-200 rounded bg-gray-200 text-gray-500 cursor-pointer drag-none'
|
||||
: 'p-2 border border-gray-200 rounded bg-green-50 text-green-700 cursor-move hover:bg-green-100 hover:underline'
|
||||
}
|
||||
|
||||
// If there are related collections passed in, fill dropCollections with these.
|
||||
if (props.relatedCollections && props.relatedCollections.length > 0) {
|
||||
selectedCollectionList.value = props.relatedCollections;
|
||||
}
|
||||
|
||||
// Add a computed property for the disabled state based on dropCollections length
|
||||
const isSaveDisabled = computed(() => selectedCollectionList.value.length === 0);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.btn-primary {
|
||||
background-color: #4f46e5;
|
||||
color: white;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #4338ca;
|
||||
}
|
||||
|
||||
.btn-primary:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px #fff, 0 0 0 4px #4f46e5;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: white;
|
||||
color: #374151;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
.btn-secondary:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px #fff, 0 0 0 4px #6366f1;
|
||||
}
|
||||
</style>
|
853
resources/js/Pages/Editor/Dataset/Edit.vue
Normal file
853
resources/js/Pages/Editor/Dataset/Edit.vue
Normal file
|
@ -0,0 +1,853 @@
|
|||
<template>
|
||||
<LayoutAuthenticated>
|
||||
|
||||
<Head title="Edit dataset" />
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton :icon="mdiImageText" title="Update dataset" main>
|
||||
<BaseButton :route-name="stardust.route('editor.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" />
|
||||
|
||||
<CardBox :form="true">
|
||||
<!-- <FormValidationErrors v-bind:errors="errors" /> -->
|
||||
<div class="mb-4">
|
||||
<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="text"
|
||||
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="text"
|
||||
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="text"
|
||||
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">
|
||||
<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" :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">
|
||||
<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>
|
||||
|
||||
<!-- download file list -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-medium text-gray-700 mb-2 flex items-center">
|
||||
Files
|
||||
<div class="inline-block relative ml-2 group">
|
||||
<button
|
||||
class="w-5 h-5 rounded-full bg-gray-200 text-gray-600 text-xs flex items-center justify-center focus:outline-none hover:bg-gray-300">
|
||||
i
|
||||
</button>
|
||||
<div
|
||||
class="absolute left-0 top-full mt-1 w-64 bg-white shadow-lg rounded-md p-3 text-xs text-left z-50 transform scale-0 origin-top-left transition-transform duration-100 group-hover:scale-100">
|
||||
<p class="text-gray-700">
|
||||
As a research data repository editor, you can only download the submitted files.
|
||||
Files cannot be
|
||||
edited or replaced at this stage.
|
||||
</p>
|
||||
<div class="absolute -top-1 left-1 w-2 h-2 bg-white transform rotate-45"></div>
|
||||
</div>
|
||||
</div>
|
||||
</h3>
|
||||
|
||||
<div v-if="form.files && form.files.length > 0" class="bg-white rounded-lg shadow overflow-hidden">
|
||||
<ul class="divide-y divide-gray-200">
|
||||
<li v-for="file in form.files" :key="file.id"
|
||||
class="px-4 py-3 flex items-center justify-between hover:bg-gray-50">
|
||||
<div class="flex items-center space-x-3 flex-1">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-6 w-6 text-gray-400" xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm font-medium text-gray-900 truncate">
|
||||
{{ file.label }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 truncate">
|
||||
{{ getFileSize(file) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-2 flex-shrink-0 flex space-x-2">
|
||||
<a v-if="file.id != undefined"
|
||||
:href="stardust.route('editor.file.download', [file.id])"
|
||||
class="inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded text-blue-700 bg-blue-100 hover:bg-blue-200">
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<BaseButtons>
|
||||
<BaseButton @click.stop="submit" :disabled="form.processing" label="Save" color="info"
|
||||
:class="{ 'opacity-25': form.processing }" small>
|
||||
</BaseButton>
|
||||
<!-- <button :disabled="form.processing" :class="{ 'opacity-25': form.processing }"
|
||||
class="text-base hover:scale-110 focus:outline-none flex justify-center px-4 py-2 rounded font-bold cursor-pointer hover:bg-teal-200 bg-teal-100 text-teal-700 border duration-200 ease-in-out border-teal-600 transition"
|
||||
@click.stop="submit">
|
||||
Save
|
||||
</button> -->
|
||||
</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 { Component, Vue, Prop, Setup, toNative } from 'vue-facing-decorator';
|
||||
// import AuthLayout from '@/Layouts/Auth.vue';
|
||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||
import { useForm, Head, usePage } from '@inertiajs/vue3';
|
||||
import { computed, ComputedRef } from 'vue';
|
||||
import { Dataset, Title, Subject, 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 { 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
|
||||
} 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: () => ({}),
|
||||
},
|
||||
|
||||
|
||||
|
||||
});
|
||||
const flash: ComputedRef<any> = computed(() => {
|
||||
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';
|
||||
|
||||
// const downloadFile = async (id: string): Promise<string> => {
|
||||
// const response = await axios.get<Blob>(`/api/download/${id}`, {
|
||||
// responseType: 'blob',
|
||||
// });
|
||||
// const url = URL.createObjectURL(response.data);
|
||||
// setTimeout(() => {
|
||||
// URL.revokeObjectURL(url);
|
||||
// }, 1000);
|
||||
// return url;
|
||||
// };
|
||||
|
||||
// for (const file of props.dataset.files) {
|
||||
// // console.log(`${file.name} path is ${file.filePath} here.`);
|
||||
// file.fileSrc = ref("");
|
||||
// // downloadFile(file.id).then((value: string) => {
|
||||
// // file.fileSrc = ref(value);
|
||||
// // form = useForm<Dataset>(props.dataset as Dataset);
|
||||
// // });
|
||||
// }
|
||||
|
||||
// props.dataset.filesToDelete = [];
|
||||
props.dataset.subjectsToDelete = [];
|
||||
props.dataset.referencesToDelete = [];
|
||||
let form = useForm<Dataset>(props.dataset as Dataset);
|
||||
|
||||
// const mainService = MainService();
|
||||
// mainService.fetchfiles(props.dataset);
|
||||
|
||||
|
||||
const submit = async (): Promise<void> => {
|
||||
let route = stardust.route('editor.dataset.update', [props.dataset.id]);
|
||||
// await Inertia.post('/app/register', this.form);
|
||||
// await router.post('/app/register', this.form);
|
||||
|
||||
|
||||
let licenses = form.licenses.map((obj) => {
|
||||
if (hasIdAttribute(obj)) {
|
||||
return obj.id.toString()
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
});
|
||||
|
||||
await form
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
licenses: licenses,
|
||||
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.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 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 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);
|
||||
};
|
||||
|
||||
const getFileSize = (file: File) => {
|
||||
if (file.size > 1024) {
|
||||
if (file.size > 1048576) {
|
||||
return Math.round(file.size / 1048576) + 'mb';
|
||||
} else {
|
||||
return Math.round(file.size / 1024) + 'kb';
|
||||
}
|
||||
} else {
|
||||
return file.size + 'b';
|
||||
}
|
||||
}
|
||||
</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>
|
|
@ -2,7 +2,7 @@
|
|||
// import { Head, Link, useForm, usePage } from '@inertiajs/inertia-vue3';
|
||||
import { Head, usePage } from '@inertiajs/vue3';
|
||||
import { ComputedRef } from 'vue';
|
||||
import { mdiSquareEditOutline, mdiAlertBoxOutline, mdiShareVariant, mdiBookEdit, mdiUndo } from '@mdi/js';
|
||||
import { mdiSquareEditOutline, mdiAlertBoxOutline, mdiShareVariant, mdiBookEdit, mdiUndo, mdiLibraryShelves } from '@mdi/js';
|
||||
import { computed } from 'vue';
|
||||
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
|
||||
import SectionMain from '@/Components/SectionMain.vue';
|
||||
|
@ -96,8 +96,8 @@ const formatServerState = (state: string) => {
|
|||
|
||||
<Head title="Editor Datasets" />
|
||||
<SectionMain>
|
||||
|
||||
|
||||
|
||||
|
||||
<NotificationBar v-if="flash.message" color="success" :icon="mdiAlertBoxOutline">
|
||||
{{ flash.message }}
|
||||
</NotificationBar>
|
||||
|
@ -108,6 +108,7 @@ const formatServerState = (state: string) => {
|
|||
{{ flash.error }}
|
||||
</NotificationBar>
|
||||
|
||||
|
||||
<!-- table -->
|
||||
<CardBox class="mb-6" has-table>
|
||||
<div v-if="props.datasets.data.length > 0">
|
||||
|
@ -141,12 +142,14 @@ const formatServerState = (state: string) => {
|
|||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
<tr v-for="dataset in props.datasets.data" :key="dataset.id"
|
||||
:class="[getRowClass(dataset)]">
|
||||
<td data-label="Login" class="py-4 whitespace-nowrap text-gray-700 dark:text-white">
|
||||
<!-- <Link v-bind:href="stardust.route('user.show', [user.id])"
|
||||
<td data-label="Login"
|
||||
class="py-4 whitespace-nowrap text-gray-700 dark:text-white table-title">
|
||||
<!-- <Link v-bind:href="stardust.route('settings.user.show', [user.id])"
|
||||
class="no-underline hover:underline text-cyan-600 dark:text-cyan-400">
|
||||
{{ user.login }}
|
||||
</Link> -->
|
||||
<div class="text-sm font-medium">{{ dataset.main_title }}</div>
|
||||
<!-- {{ user.id }} -->
|
||||
{{ dataset.main_title }}
|
||||
</td>
|
||||
<td class="py-4 whitespace-nowrap text-gray-700 dark:text-white">
|
||||
<div class="text-sm">{{ dataset.user.login }}</div>
|
||||
|
@ -178,33 +181,46 @@ const formatServerState = (state: string) => {
|
|||
</td>
|
||||
<td
|
||||
class="py-4 whitespace-nowrap text-right text-sm font-medium text-gray-700 dark:text-white">
|
||||
<BaseButtons type="justify-start lg:justify-end" no-wrap>
|
||||
<BaseButton v-if="can.receive && (dataset.server_state == 'released')"
|
||||
:route-name="stardust.route('editor.dataset.receive', [dataset.id])"
|
||||
color="info" :icon="mdiSquareEditOutline" :label="'Receive edit task'"
|
||||
small />
|
||||
<div type="justify-start lg:justify-end" class="grid grid-cols-2 gap-x-2 gap-y-2" no-wrap>
|
||||
|
||||
<BaseButton v-if="can.receive && (dataset.server_state == 'released')"
|
||||
:route-name="stardust.route('editor.dataset.receive', [dataset.id])"
|
||||
color="info" :icon="mdiSquareEditOutline" :label="'Receive edit task'"
|
||||
small class="col-span-1"/>
|
||||
|
||||
<BaseButton
|
||||
v-if="can.approve && (dataset.server_state == 'editor_accepted' || dataset.server_state == 'rejected_reviewer')"
|
||||
:route-name="stardust.route('editor.dataset.approve', [dataset.id])"
|
||||
color="info" :icon="mdiShareVariant" :label="'Approve'" small />
|
||||
<BaseButton
|
||||
v-if="can.approve && (dataset.server_state == 'editor_accepted' || dataset.server_state == 'rejected_reviewer')"
|
||||
:route-name="stardust.route('editor.dataset.approve', [dataset.id])"
|
||||
color="info" :icon="mdiShareVariant" :label="'Approve'" small class="col-span-1"/>
|
||||
|
||||
<BaseButton
|
||||
v-if="can.approve && (dataset.server_state == 'editor_accepted' || dataset.server_state == 'rejected_reviewer')"
|
||||
:route-name="stardust.route('editor.dataset.reject', [dataset.id])"
|
||||
color="info" :icon="mdiUndo" label="Reject" small>
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="can.approve && (dataset.server_state == 'editor_accepted' || dataset.server_state == 'rejected_reviewer')"
|
||||
:route-name="stardust.route('editor.dataset.reject', [dataset.id])"
|
||||
color="info" :icon="mdiUndo" label="'Reject'" small class="col-span-1">
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton v-if="can.publish && (dataset.server_state == 'reviewed')"
|
||||
:route-name="stardust.route('editor.dataset.publish', [dataset.id])"
|
||||
color="info" :icon="mdiBookEdit" :label="'Publish'" small />
|
||||
<BaseButton
|
||||
v-if="can.edit && (dataset.server_state == 'editor_accepted' || dataset.server_state == 'rejected_reviewer')"
|
||||
:route-name="stardust.route('editor.dataset.edit', [Number(dataset.id)])"
|
||||
color="info" :icon="mdiSquareEditOutline" :label="'Edit'" small class="col-span-1">
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton
|
||||
v-if="can.publish && (dataset.server_state == 'published' && !dataset.identifier)"
|
||||
:route-name="stardust.route('editor.dataset.doi', [dataset.id])"
|
||||
color="info" :icon="mdiBookEdit" :label="'Mint DOI'" small />
|
||||
<BaseButton
|
||||
v-if="can.edit && (dataset.server_state == 'editor_accepted' || dataset.server_state == 'rejected_reviewer')"
|
||||
:route-name="stardust.route('editor.dataset.categorize', [dataset.id])"
|
||||
color="info" :icon="mdiLibraryShelves" :label="'Sets'" small class="col-span-1">
|
||||
</BaseButton>
|
||||
|
||||
</BaseButtons>
|
||||
<BaseButton v-if="can.publish && (dataset.server_state == 'reviewed')"
|
||||
:route-name="stardust.route('editor.dataset.publish', [dataset.id])"
|
||||
color="info" :icon="mdiBookEdit" :label="'Publish'" small class="col-span-1"/>
|
||||
|
||||
<BaseButton
|
||||
v-if="can.publish && (dataset.server_state == 'published' && !dataset.identifier)"
|
||||
:route-name="stardust.route('editor.dataset.doi', [dataset.id])"
|
||||
color="info" :icon="mdiBookEdit" :label="'Mint DOI'" small class="col-span-1 last-in-row"/>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -231,3 +247,18 @@ const formatServerState = (state: string) => {
|
|||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
||||
|
||||
|
||||
<style scoped lang="css">
|
||||
.table-title {
|
||||
max-width: 200px;
|
||||
/* set a maximum width */
|
||||
overflow: hidden;
|
||||
/* hide overflow */
|
||||
text-overflow: ellipsis;
|
||||
/* show ellipsis for overflowed text */
|
||||
white-space: nowrap;
|
||||
/* prevent wrapping */
|
||||
}
|
||||
|
||||
</style>
|
|
@ -295,7 +295,7 @@ const fetchCollections = async (collectionId: number) => {
|
|||
return alreadyDropped ? { ...collection, inUse: true } : { ...collection, inUse: false };
|
||||
});
|
||||
// Check if selected collection is in the selected list
|
||||
if (selectedCollection.value && selectedCollectionList.value.find(dc => dc.id === selectedCollection.value.id)) {
|
||||
if (selectedCollection.value && selectedCollectionList.value.find(dc => dc.id === selectedCollection.value?.id)) {
|
||||
selectedCollection.value = { ...selectedCollection.value, inUse: true };
|
||||
} else if (selectedCollection.value) {
|
||||
selectedCollection.value = { ...selectedCollection.value, inUse: false };
|
||||
|
|
|
@ -544,15 +544,15 @@ Removes a selected keyword
|
|||
<div class="flex items-center">
|
||||
<!-- <label>{{ form.titles[0].language }}</label>
|
||||
<label>{{ form.language }}</label> -->
|
||||
<icon-wizard :is-current="formStep == 1" :is-checked="formStep > 1" :label="'Language'">
|
||||
<icon-wizard :is-current="formStep == 1" :is-checked="formStep > 1" :label="'Step 1'">
|
||||
<icon-language></icon-language>
|
||||
</icon-wizard>
|
||||
|
||||
<icon-wizard :is-current="formStep == 2" :is-checked="formStep > 2" :label="'Mandatory'">
|
||||
<icon-wizard :is-current="formStep == 2" :is-checked="formStep > 2" :label="'Step 2'">
|
||||
<icon-mandatory></icon-mandatory>
|
||||
</icon-wizard>
|
||||
|
||||
<icon-wizard :is-current="formStep == 3" :is-checked="formStep > 3" :label="'Recommended'">
|
||||
<icon-wizard :is-current="formStep == 3" :is-checked="formStep > 3" :label="'Step 3'">
|
||||
<icon-recommendet></icon-recommendet>
|
||||
</icon-wizard>
|
||||
|
||||
|
@ -588,7 +588,7 @@ Removes a selected keyword
|
|||
<input class="form-checkbox" name="rights" id="rights" type="checkbox" v-model="dataset.rights" />
|
||||
terms and conditions
|
||||
</label> -->
|
||||
<FormField label="Rights" help="You must agree to continue" wrap-body
|
||||
<FormField label="Rights" help="You must agree that you have read the Terms and Conditions. Please click on the 'i' icon to find a read the policy" wrap-body
|
||||
:class="{ 'text-red-400': form.errors.rights }" class="mt-8 w-full mx-2 flex-1 flex-col">
|
||||
<label for="rights" class="checkbox mr-6 mb-3 last:mr-0">
|
||||
<input type="checkbox" id="rights" required v-model="form.rights" />
|
||||
|
|
|
@ -42,7 +42,8 @@
|
|||
<!-- (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" />
|
||||
<FormCheckRadioGroup type="radio" v-model="form.licenses" name="licenses" is-column
|
||||
:options="licenses" />
|
||||
</FormField>
|
||||
|
||||
<div class="flex flex-col md:flex-row">
|
||||
|
@ -163,7 +164,8 @@
|
|||
: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">
|
||||
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(', ') }}
|
||||
|
@ -176,7 +178,7 @@
|
|||
<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>
|
||||
|
@ -243,8 +245,9 @@
|
|||
<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" :relation="'authors'"/>
|
||||
<div class="text-red-400 text-sm" v-if="form.errors.authors && Array.isArray(form.errors.authors)">
|
||||
<TablePersons :persons="form.authors" v-if="form.authors.length > 0" :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>
|
||||
|
@ -334,8 +337,8 @@
|
|||
</FormField>
|
||||
</div>
|
||||
|
||||
<CardBox class="mb-6 shadow" has-table title="Dataset References" :icon="mdiEarthPlus" :header-icon="mdiPlusCircle"
|
||||
v-on:header-icon-click="addReference">
|
||||
<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>
|
||||
|
@ -408,6 +411,42 @@
|
|||
</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 />
|
||||
|
@ -420,7 +459,7 @@
|
|||
</li>
|
||||
</ul> -->
|
||||
<TableKeywords :keywords="form.subjects" :errors="form.errors" :subjectTypes="subjectTypes"
|
||||
v-if="form.subjects.length > 0" />
|
||||
v-model:subjects-to-delete="form.subjectsToDelete" v-if="form.subjects.length > 0" />
|
||||
</CardBox>
|
||||
|
||||
</div>
|
||||
|
@ -447,7 +486,9 @@
|
|||
</select> -->
|
||||
</div>
|
||||
|
||||
<FileUploadComponent v-model:files="form.files" v-model:filesToDelete="form.filesToDelete" :showClearButton="false"></FileUploadComponent>
|
||||
<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(', ') }}
|
||||
|
@ -475,8 +516,8 @@
|
|||
</BaseButtons>
|
||||
</template>
|
||||
</CardBox>
|
||||
<!-- Loading Spinner -->
|
||||
<div v-if="form.processing"
|
||||
<!-- 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">
|
||||
|
@ -527,6 +568,7 @@ import {
|
|||
mdiBookOpenPageVariant,
|
||||
mdiEarthPlus,
|
||||
mdiAlertBoxOutline,
|
||||
mdiRestore
|
||||
} from '@mdi/js';
|
||||
import { notify } from '@/notiwind';
|
||||
import NotificationBar from '@/Components/NotificationBar.vue';
|
||||
|
@ -633,6 +675,8 @@ const mapId = 'test';
|
|||
// }
|
||||
|
||||
props.dataset.filesToDelete = [];
|
||||
props.dataset.subjectsToDelete = [];
|
||||
props.dataset.referencesToDelete = [];
|
||||
let form = useForm<Dataset>(props.dataset as Dataset);
|
||||
|
||||
// const mainService = MainService();
|
||||
|
@ -701,13 +745,13 @@ const submit = async (): Promise<void> => {
|
|||
// 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 {
|
||||
} else {
|
||||
// return normal request input
|
||||
fileInputs.push(obj);
|
||||
}
|
||||
|
@ -744,7 +788,9 @@ const submit = async (): Promise<void> => {
|
|||
// formStep.value++;
|
||||
// form.filesToDelete = [];
|
||||
// Clear the array using splice
|
||||
form.filesToDelete?.splice(0, form.filesToDelete.length);
|
||||
form.filesToDelete?.splice(0, form.filesToDelete.length);
|
||||
form.subjectsToDelete?.splice(0, form.subjectsToDelete.length);
|
||||
form.referencesToDelete?.splice(0, form.referencesToDelete.length);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -794,7 +840,7 @@ const onAddContributor = (person: Person) => {
|
|||
};
|
||||
|
||||
const addKeyword = () => {
|
||||
let newSubject: Subject = { value: 'test', language: '', type: 'uncontrolled', dataset_count: 0 };
|
||||
let newSubject: Subject = { value: '', language: '', type: 'uncontrolled' };
|
||||
//this.dataset.files.push(uploadedFiles[i]);
|
||||
form.subjects.push(newSubject);
|
||||
};
|
||||
|
@ -806,9 +852,35 @@ const addReference = () => {
|
|||
};
|
||||
|
||||
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);
|
||||
};
|
||||
|
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue