### Major Features - Add comprehensive ORCID validation with checksum verification - Implement unsaved changes detection and auto-save functionality - Enhanced form component reactivity and state management ### ORCID Implementation - Create custom VineJS ORCID validation rule with MOD-11-2 algorithm - Add ORCID fields to Person model and TablePersons component - Update dataset validators to include ORCID validation - Add descriptive placeholder text for ORCID input fields ### UI/UX Improvements - Add UnsavedChangesWarning component with detailed change tracking - Improve FormCheckRadio and FormCheckRadioGroup reactivity - Enhanced BaseButton with proper disabled state handling - Better error handling and user feedback in file validation ### Data Management - Implement sophisticated change detection for all dataset fields - Add proper handling of array ordering for authors/contributors - Improve license selection with better state management - Enhanced subject/keyword processing with duplicate detection ### Technical Improvements - Optimize search indexing with conditional updates based on modification dates - Update person model column mapping for ORCID - Improve validation error messages and user guidance - Better handling of file uploads and deletion tracking ### Dependencies - Update various npm packages (AWS SDK, Babel, Vite, etc.) - Add baseline-browser-mapping for better browser compatibility ### Bug Fixes - Fix form reactivity issues with checkbox/radio groups - Improve error handling in file validation rules - Better handling of edge cases in change detection
166 lines
6.1 KiB
Vue
166 lines
6.1 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref, PropType } from 'vue';
|
|
import FormCheckRadio from '@/Components/FormCheckRadio.vue';
|
|
// import BaseButton from '@/Components/BaseButton.vue';
|
|
// import FormControl from '@/Components/FormControl.vue';
|
|
|
|
const props = defineProps({
|
|
options: {
|
|
type: Object,
|
|
default: () => { },
|
|
},
|
|
allowManualAdding: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
manualAddingPlaceholder: {
|
|
type: String,
|
|
default: 'Add manually',
|
|
required: false,
|
|
},
|
|
name: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
type: {
|
|
type: String as PropType<'checkbox' | 'radio' | 'switch'>,
|
|
default: 'checkbox',
|
|
validator: (value: string) => ['checkbox', 'radio', 'switch'].includes(value),
|
|
},
|
|
componentClass: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
isColumn: Boolean,
|
|
modelValue: {
|
|
type: [Array, String, Number, Boolean, Object],
|
|
default: null,
|
|
},
|
|
});
|
|
const emit = defineEmits(['update:modelValue']);
|
|
// const computedValue = computed({
|
|
// // get: () => props.modelValue,
|
|
// get: () => {
|
|
// // const ids = props.modelValue.map((obj) => obj.id);
|
|
// // return ids;
|
|
// if (Array.isArray(props.modelValue)) {
|
|
// if (props.modelValue.every((item) => typeof item === 'number')) {
|
|
// return props.modelValue;
|
|
// } else if (props.modelValue.every((item) => hasIdAttribute(item))) {
|
|
// const ids = props.modelValue.map((obj) => obj.id);
|
|
// return ids;
|
|
// }
|
|
// return props.modelValue;
|
|
// }
|
|
// // return props.modelValue;
|
|
// },
|
|
// set: (value) => {
|
|
// emit('update:modelValue', value);
|
|
// },
|
|
// });
|
|
|
|
// Define a type guard to check if an object has an 'id' attribute
|
|
// function hasIdAttribute(obj: any): obj is { id: any } {
|
|
// return typeof obj === 'object' && 'id' in obj;
|
|
// }
|
|
|
|
const computedValue = computed({
|
|
get: () => {
|
|
if (!props.modelValue) return props.modelValue;
|
|
|
|
if (Array.isArray(props.modelValue)) {
|
|
// Handle empty array
|
|
if (props.modelValue.length === 0) return [];
|
|
|
|
// If all items are objects with id property
|
|
if (props.modelValue.every((item) => hasIdAttribute(item))) {
|
|
return props.modelValue.map((obj) => {
|
|
// Ensure we return the correct type based on the options keys
|
|
const id = obj.id;
|
|
// Check if options keys are numbers or strings
|
|
const optionKeys = Object.keys(props.options);
|
|
if (optionKeys.length > 0) {
|
|
// If option keys are numeric strings, return number
|
|
if (optionKeys.every(key => !isNaN(Number(key)))) {
|
|
return Number(id);
|
|
}
|
|
}
|
|
return String(id);
|
|
});
|
|
}
|
|
|
|
// If all items are numbers
|
|
if (props.modelValue.every((item) => typeof item === 'number')) {
|
|
return props.modelValue;
|
|
}
|
|
|
|
// If all items are strings that represent numbers
|
|
if (props.modelValue.every((item) => typeof item === 'string' && !isNaN(Number(item)))) {
|
|
// Convert to numbers if options keys are numeric
|
|
const optionKeys = Object.keys(props.options);
|
|
if (optionKeys.length > 0 && optionKeys.every(key => !isNaN(Number(key)))) {
|
|
return props.modelValue.map(item => Number(item));
|
|
}
|
|
return props.modelValue;
|
|
}
|
|
|
|
// Return as-is for other cases
|
|
return props.modelValue;
|
|
}
|
|
|
|
return props.modelValue;
|
|
},
|
|
set: (value) => {
|
|
emit('update:modelValue', value);
|
|
},
|
|
});
|
|
const hasIdAttribute = (obj: any): obj is { id: any } => {
|
|
return typeof obj === 'object' && 'id' in obj;
|
|
};
|
|
|
|
const newOption = ref<string>('');
|
|
const addOption = () => {
|
|
if (newOption.value && !props.options[newOption.value]) {
|
|
props.options[newOption.value] = newOption.value;
|
|
newOption.value = '';
|
|
}
|
|
};
|
|
|
|
const inputElClass = computed(() => {
|
|
const base = [
|
|
'px-3 py-2 max-w-full border-gray-700 rounded w-full',
|
|
'dark:placeholder-gray-400',
|
|
'h-12',
|
|
'border',
|
|
'bg-transparent'
|
|
// props.isReadOnly ? 'bg-gray-50 dark:bg-slate-600' : 'bg-white dark:bg-slate-800',
|
|
];
|
|
// if (props.icon) {
|
|
// base.push('pl-10');
|
|
// }
|
|
return base;
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="flex justify-start flex-wrap -mb-3" :class="{ 'flex-col': isColumn }">
|
|
<!-- :input-value="key" -->
|
|
<!-- :label="value" -->
|
|
<!-- :input-value="value.id"
|
|
:label="value.name" -->
|
|
<div v-if="allowManualAdding && type === 'checkbox'" class="flex items-center mt-2 mb-2 relative">
|
|
<input v-model="newOption" :placeholder="manualAddingPlaceholder" :class="inputElClass"
|
|
@keydown.prevent.enter="addOption" />
|
|
<svg v-show="newOption.length >= 2" @click.prevent="addOption" xmlns="http://www.w3.org/2000/svg"
|
|
class="w-6 h-6 absolute right-2 top-1/2 transform -translate-y-1/2 cursor-pointer text-gray-500"
|
|
viewBox="0 0 24 24" fill="currentColor">
|
|
<path
|
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-6H5v-2h6V5h2v6h6v2h-6v6z" />
|
|
</svg>
|
|
</div>
|
|
<!-- <FormCheckRadio v-for="(value, key) in options" :key="key" v-model="computedValue" :type="type"
|
|
:name="name" :input-value="key" :label="value" :class="componentClass" /> -->
|
|
<FormCheckRadio v-for="(value, key) in options" key="`${name}-${key}-${JSON.stringify(computedValue)}`" v-model="computedValue" :type="type"
|
|
:name="name" :input-value="isNaN(Number(key)) ? key : Number(key)" :label="value" :class="componentClass" />
|
|
</div>
|
|
</template>
|