hotfix(dataset): enhance radio button and checkbox components and add arrayContainsTypes validation
- Added checkbox support to the `FormCheckRadio` component. - Updated the styling of the radio button and checkbox components. - Added the `arrayContainsTypes` validation rule to ensure that arrays contain specific types. - Updated the `dataset` validators and controllers to use the new validation rule. - Updated the `FormCheckRadioGroup` component to correctly handle the `input-value` as a number. - Removed the default value from the `id` column in the `collections` migration. - Added the `array_contains_types` rule to the `adonisrc.ts` file.
This commit is contained in:
parent
9823364670
commit
09f65359f9
8 changed files with 116 additions and 41 deletions
|
@ -35,6 +35,7 @@ export default defineConfig({
|
||||||
() => import('#start/rules/dependent_array_min_length'),
|
() => import('#start/rules/dependent_array_min_length'),
|
||||||
() => import('#start/rules/referenceValidation'),
|
() => import('#start/rules/referenceValidation'),
|
||||||
() => import('#start/rules/valid_mimetype'),
|
() => import('#start/rules/valid_mimetype'),
|
||||||
|
() => import('#start/rules/array_contains_types'),
|
||||||
],
|
],
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
@ -207,7 +207,8 @@ export default class DatasetController {
|
||||||
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.minLength(1),
|
// .minLength(2)
|
||||||
|
.arrayContainsTypes({ typeA: 'main', typeB: 'translated' }),
|
||||||
descriptions: vine
|
descriptions: vine
|
||||||
.array(
|
.array(
|
||||||
vine.object({
|
vine.object({
|
||||||
|
@ -221,7 +222,8 @@ export default class DatasetController {
|
||||||
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.minLength(1),
|
// .minLength(1),
|
||||||
|
.arrayContainsTypes({ typeA: 'abstract', typeB: 'translated' }),
|
||||||
authors: vine
|
authors: vine
|
||||||
.array(
|
.array(
|
||||||
vine.object({
|
vine.object({
|
||||||
|
@ -270,7 +272,6 @@ export default class DatasetController {
|
||||||
}
|
}
|
||||||
return response.redirect().back();
|
return response.redirect().back();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async thirdStep({ request, response }: HttpContext) {
|
public async thirdStep({ request, response }: HttpContext) {
|
||||||
const newDatasetSchema = vine.object({
|
const newDatasetSchema = vine.object({
|
||||||
// first step
|
// first step
|
||||||
|
@ -296,7 +297,8 @@ export default class DatasetController {
|
||||||
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.minLength(1),
|
// .minLength(2)
|
||||||
|
.arrayContainsTypes({ typeA: 'main', typeB: 'translated' }),
|
||||||
descriptions: vine
|
descriptions: vine
|
||||||
.array(
|
.array(
|
||||||
vine.object({
|
vine.object({
|
||||||
|
@ -310,7 +312,8 @@ export default class DatasetController {
|
||||||
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.minLength(1),
|
// .minLength(1),
|
||||||
|
.arrayContainsTypes({ typeA: 'abstract', typeB: 'translated' }),
|
||||||
authors: vine
|
authors: vine
|
||||||
.array(
|
.array(
|
||||||
vine.object({
|
vine.object({
|
||||||
|
@ -539,11 +542,6 @@ export default class DatasetController {
|
||||||
try {
|
try {
|
||||||
await multipart.process();
|
await multipart.process();
|
||||||
// // Instead of letting an error abort the controller, check if any error occurred
|
// // Instead of letting an error abort the controller, check if any error occurred
|
||||||
// if (fileUploadError) {
|
|
||||||
// // Flash the error and return an inertia view that shows the error message.
|
|
||||||
// session.flash('errors', { 'upload error': [fileUploadError.message] });
|
|
||||||
// return response.redirect().back();
|
|
||||||
// }
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// This is where you'd expect to catch any errors.
|
// This is where you'd expect to catch any errors.
|
||||||
session.flash('errors', error.messages);
|
session.flash('errors', error.messages);
|
||||||
|
|
|
@ -40,7 +40,8 @@ export const createDatasetValidator = vine.compile(
|
||||||
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.minLength(1),
|
// .minLength(2)
|
||||||
|
.arrayContainsTypes({ typeA: 'main', typeB: 'translated' }),
|
||||||
descriptions: vine
|
descriptions: vine
|
||||||
.array(
|
.array(
|
||||||
vine.object({
|
vine.object({
|
||||||
|
@ -54,7 +55,8 @@ export const createDatasetValidator = vine.compile(
|
||||||
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.minLength(1),
|
// .minLength(1),
|
||||||
|
.arrayContainsTypes({ typeA: 'abstract', typeB: 'translated' }),
|
||||||
authors: vine
|
authors: vine
|
||||||
.array(
|
.array(
|
||||||
vine.object({
|
vine.object({
|
||||||
|
@ -156,8 +158,7 @@ export const createDatasetValidator = vine.compile(
|
||||||
.fileScan({ removeInfected: true }),
|
.fileScan({ removeInfected: true }),
|
||||||
)
|
)
|
||||||
.minLength(1),
|
.minLength(1),
|
||||||
}),
|
}),);
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the dataset's update action
|
* Validates the dataset's update action
|
||||||
|
@ -187,7 +188,8 @@ export const updateDatasetValidator = vine.compile(
|
||||||
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.minLength(1),
|
// .minLength(2)
|
||||||
|
.arrayContainsTypes({ typeA: 'main', typeB: 'translated' }),
|
||||||
descriptions: vine
|
descriptions: vine
|
||||||
.array(
|
.array(
|
||||||
vine.object({
|
vine.object({
|
||||||
|
@ -201,7 +203,7 @@ export const updateDatasetValidator = vine.compile(
|
||||||
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
.translatedLanguage({ mainLanguageField: 'language', typeField: 'type' }),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.minLength(1),
|
.arrayContainsTypes({ typeA: 'abstract', typeB: 'translated' }),
|
||||||
authors: vine
|
authors: vine
|
||||||
.array(
|
.array(
|
||||||
vine.object({
|
vine.object({
|
||||||
|
|
|
@ -5,7 +5,7 @@ export default class Collections extends BaseSchema {
|
||||||
|
|
||||||
public async up() {
|
public async up() {
|
||||||
this.schema.createTable(this.tableName, (table) => {
|
this.schema.createTable(this.tableName, (table) => {
|
||||||
table.increments('id').defaultTo("nextval('collections_id_seq')");
|
table.increments('id');//.defaultTo("nextval('collections_id_seq')");
|
||||||
table.integer('role_id').unsigned();
|
table.integer('role_id').unsigned();
|
||||||
table
|
table
|
||||||
.foreign('role_id', 'collections_role_id_foreign')
|
.foreign('role_id', 'collections_role_id_foreign')
|
||||||
|
|
|
@ -56,27 +56,15 @@ const isChecked = computed(() => {
|
||||||
return Array.isArray(computedValue.value) &&
|
return Array.isArray(computedValue.value) &&
|
||||||
computedValue.value.length > 0 &&
|
computedValue.value.length > 0 &&
|
||||||
computedValue.value[0] === props.inputValue;
|
computedValue.value[0] === props.inputValue;
|
||||||
|
} else if (props.type === 'checkbox') {
|
||||||
|
return Array.isArray(computedValue.value) &&
|
||||||
|
computedValue.value.length > 0 &&
|
||||||
|
computedValue.value.includes(props.inputValue);
|
||||||
}
|
}
|
||||||
return computedValue.value === props.inputValue;
|
return computedValue.value === props.inputValue;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<!-- <template>
|
|
||||||
<label v-if="type == 'radio'" :class="[type, 'mr-6 mb-3 last:mr-0 inline-flex items-center cursor-pointer relative']">
|
|
||||||
<input v-model="computedValue" :type="inputType" :name="name" :value="inputValue" class="absolute left-0 opacity-0 -z-1 focus:outline-none focus:ring-0" />
|
|
||||||
<span
|
|
||||||
class="check border-gray-700 border transition-colors duration-200 dark:bg-slate-800 block w-5 h-5 rounded-full"
|
|
||||||
:class="{ 'bg-no-repeat bg-center bg-blue-600 border-blue-600 border-4': isChecked }"/>
|
|
||||||
<span class="pl-2">{{ label }}</span>
|
|
||||||
</label>
|
|
||||||
<label v-else :class="[type, 'mr-6 mb-3 last:mr-0']">
|
|
||||||
<input v-model="computedValue" :type="inputType" :name="name" :value="inputValue" />
|
|
||||||
<span class="check" />
|
|
||||||
<span class="pl-2">{{ label }}</span>
|
|
||||||
</label>
|
|
||||||
</template> -->
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<label v-if="type === 'radio'" :class="[type]"
|
<label v-if="type === 'radio'" :class="[type]"
|
||||||
class="mr-6 mb-3 last:mr-0 inline-flex items-center cursor-pointer relative">
|
class="mr-6 mb-3 last:mr-0 inline-flex items-center cursor-pointer relative">
|
||||||
|
@ -85,14 +73,19 @@ const isChecked = computed(() => {
|
||||||
:checked="isChecked" />
|
:checked="isChecked" />
|
||||||
<span class="check border transition-colors duration-200 dark:bg-slate-800 block w-5 h-5 rounded-full" :class="{
|
<span class="check border transition-colors duration-200 dark:bg-slate-800 block w-5 h-5 rounded-full" :class="{
|
||||||
'border-gray-700': !isChecked,
|
'border-gray-700': !isChecked,
|
||||||
'bg-radio-checked bg-no-repeat bg-center bg-blue-600 border-blue-600 border-4': isChecked
|
'bg-radio-checked bg-no-repeat bg-center bg-lime-600 border-lime-600 border-4': isChecked
|
||||||
}" />
|
}" />
|
||||||
<span class="pl-2">{{ label }}</span>
|
<span class="pl-2 control-label">{{ label }}</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label v-else class="mr-6 mb-3 last:mr-0" :class="[type]">
|
<label v-else-if="type === 'checkbox'" :class="[type]"
|
||||||
<input v-model="computedValue" :type="inputType" :name="name" :value="inputValue" />
|
class="mr-6 mb-3 last:mr-0 inline-flex items-center cursor-pointer relative">
|
||||||
<span class="check" />
|
<input v-model="computedValue" :type="inputType" :name="name" :value="inputValue"
|
||||||
<span class="pl-2">{{ label }}</span>
|
class="absolute left-0 opacity-0 -z-1 focus:outline-none focus:ring-0" />
|
||||||
|
<span class="check border transition-colors duration-200 dark:bg-slate-800 block w-5 h-5 rounded" :class="{
|
||||||
|
'border-gray-700': !isChecked,
|
||||||
|
'bg-checkbox-checked bg-no-repeat bg-center bg-lime-600 border-lime-600 border-4': isChecked
|
||||||
|
}" />
|
||||||
|
<span class="pl-2 control-label">{{ label }}</span>
|
||||||
</label>
|
</label>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -47,7 +47,7 @@ const computedValue = computed({
|
||||||
if (props.modelValue.every((item) => typeof item === 'number')) {
|
if (props.modelValue.every((item) => typeof item === 'number')) {
|
||||||
return props.modelValue;
|
return props.modelValue;
|
||||||
} else if (props.modelValue.every((item) => hasIdAttribute(item))) {
|
} else if (props.modelValue.every((item) => hasIdAttribute(item))) {
|
||||||
const ids = props.modelValue.map((obj) => obj.id.toString());
|
const ids = props.modelValue.map((obj) => obj.id);
|
||||||
return ids;
|
return ids;
|
||||||
}
|
}
|
||||||
return props.modelValue;
|
return props.modelValue;
|
||||||
|
@ -109,6 +109,6 @@ const inputElClass = computed(() => {
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<FormCheckRadio v-for="(value, key) in options" :key="key" v-model="computedValue" :type="type"
|
<FormCheckRadio v-for="(value, key) in options" :key="key" v-model="computedValue" :type="type"
|
||||||
:name="name" :input-value="key" :label="value" :class="componentClass" />
|
:name="name" :input-value="Number(key)" :label="value" :class="componentClass" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
80
start/rules/array_contains_types.ts
Normal file
80
start/rules/array_contains_types.ts
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import { FieldContext } from '@vinejs/vine/types';
|
||||||
|
import vine, { VineArray } from '@vinejs/vine';
|
||||||
|
import { SchemaTypes } from '@vinejs/vine/types';
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
typeA: string;
|
||||||
|
typeB: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom rule to validate an array of titles contains at least one title
|
||||||
|
* with type 'main' and one with type 'translated'.
|
||||||
|
*
|
||||||
|
* This rule expects the validated value to be an array of objects,
|
||||||
|
* where each object has a "type" property.
|
||||||
|
*/
|
||||||
|
async function arrayContainsTypes(value: unknown, options: Options, field: FieldContext) {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
field.report(`The {{field}} must be an array of titles.`, 'array.titlesContainsMainAndTranslated', field);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeAExpected = options.typeA.toLowerCase();
|
||||||
|
const typeBExpected = options.typeB.toLowerCase();
|
||||||
|
|
||||||
|
// const hasMain = value.some((title: any) => {
|
||||||
|
// return typeof title === 'object' && title !== null && String(title.type).toLowerCase() === 'main';
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const hasTranslated = value.some((title: any) => {
|
||||||
|
// return typeof title === 'object' && title !== null && String(title.type).toLowerCase() === 'translated';
|
||||||
|
// });
|
||||||
|
const hasTypeA = value.some((item: any) => {
|
||||||
|
return typeof item === 'object' && item !== null && String(item.type).toLowerCase() === typeAExpected;
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasTypeB = value.some((item: any) => {
|
||||||
|
return typeof item === 'object' && item !== null && String(item.type).toLowerCase() === typeBExpected;
|
||||||
|
});
|
||||||
|
if (!hasTypeA || !hasTypeB) {
|
||||||
|
let errorMessage = `The ${field.getFieldPath()} array must have at least one '${options.typeA}' item and one '${options.typeB}' item.`;
|
||||||
|
|
||||||
|
// Check for specific field names to produce a more readable message.
|
||||||
|
if (field.getFieldPath() === 'titles') {
|
||||||
|
// For titles we expect one main and minimum one translated title.
|
||||||
|
if (!hasTypeA && !hasTypeB) {
|
||||||
|
errorMessage = 'For titles, define one main title and minimum one translated title.';
|
||||||
|
} else if (!hasTypeA) {
|
||||||
|
errorMessage = 'For titles, define one main title.';
|
||||||
|
} else if (!hasTypeB) {
|
||||||
|
errorMessage = 'For titles, define minimum one translated title.';
|
||||||
|
}
|
||||||
|
} else if (field.getFieldPath() === 'descriptions') {
|
||||||
|
// For descriptions we expect one abstracts description and minimum one translated description.
|
||||||
|
if (!hasTypeA && !hasTypeB) {
|
||||||
|
errorMessage = 'For descriptions, define one abstracts description and minimum one translated description.';
|
||||||
|
} else if (!hasTypeA) {
|
||||||
|
errorMessage = 'For descriptions, define one abstracts description.';
|
||||||
|
} else if (!hasTypeB) {
|
||||||
|
errorMessage = 'For descriptions, define minimum one translated description.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
field.report(errorMessage, 'array.containsTypes', field, options);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const arrayContainsMainAndTranslatedRule = vine.createRule(arrayContainsTypes);
|
||||||
|
|
||||||
|
declare module '@vinejs/vine' {
|
||||||
|
interface VineArray<Schema extends SchemaTypes> {
|
||||||
|
arrayContainsTypes(options: Options): this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VineArray.macro('arrayContainsTypes', function <Schema extends SchemaTypes>(this: VineArray<Schema>, options: Options) {
|
||||||
|
return this.use(arrayContainsMainAndTranslatedRule(options));
|
||||||
|
});
|
|
@ -14,6 +14,7 @@ module.exports = {
|
||||||
extend: {
|
extend: {
|
||||||
backgroundImage: {
|
backgroundImage: {
|
||||||
'radio-checked': "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23fff' d='M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z' /%3E%3C/svg%3E\")",
|
'radio-checked': "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23fff' d='M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z' /%3E%3C/svg%3E\")",
|
||||||
|
'checkbox-checked': "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3Cpath style='fill:%23fff' d='M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z'%3E%3C/path%3E%3C/svg%3E\")",
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
'primary': '#22C55E',
|
'primary': '#22C55E',
|
||||||
|
|
Loading…
Add table
Reference in a new issue