|
@@ -1,10 +1,9 @@
|
|
|
-import type { ValidationRule } from 'quasar';
|
|
|
import type { Validator } from './custom-form-types';
|
|
|
-import { z } from 'zod';
|
|
|
-import { isEmail } from 'validator';
|
|
|
+import validatorjs from 'validator';
|
|
|
import type { Dayjs } from 'src/plugins/dayjs/dayjs';
|
|
|
import { dayjs } from 'src/plugins/dayjs/dayjs';
|
|
|
|
|
|
+// утилиты
|
|
|
function validateLeastYearsAgo(date: string, years: number): boolean {
|
|
|
// 1. Проверяем, что minAge является неотрицательным числом
|
|
|
if (typeof years !== 'number' || years < 0 || !Number.isInteger(years)) {
|
|
@@ -38,111 +37,157 @@ function validateLeastYearsAgo(date: string, years: number): boolean {
|
|
|
return calculatedAge >= years;
|
|
|
}
|
|
|
|
|
|
-export const getValidationFunc = (validator: Validator): ValidationRule | undefined => {
|
|
|
- switch (validator.code) {
|
|
|
- case 'required':
|
|
|
- return (v: string) => (v ? true : `Поле должно быть заполнено`);
|
|
|
+const getTypeError = (expected: 'string' | 'number' | 'date' | 'audio') => {
|
|
|
+ const types = {
|
|
|
+ string: 'строкой',
|
|
|
+ number: 'числом',
|
|
|
+ date: 'датой',
|
|
|
+ audio: 'аудио файлом',
|
|
|
+ };
|
|
|
+ return `Поле должно быть ${types[expected]}`;
|
|
|
+};
|
|
|
+const parseValidatorValue = (value: unknown): number | null => {
|
|
|
+ const num = Number(value);
|
|
|
+ return Number.isFinite(num) ? num : null;
|
|
|
+};
|
|
|
|
|
|
- // string
|
|
|
- case 'maxString': {
|
|
|
- const parsedValue = z.number().parse(Number(validator.value));
|
|
|
- return (v: string) =>
|
|
|
- v.length <= parsedValue || `Поле должно содержать максимум ${parsedValue} символов`;
|
|
|
+// Декларация для поддержки webkitAudioContext в TypeScript
|
|
|
+declare global {
|
|
|
+ interface Window {
|
|
|
+ webkitAudioContext?: typeof AudioContext;
|
|
|
+ }
|
|
|
+}
|
|
|
+async function getAudioDuration(file: File): Promise<number> {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const objectUrl = URL.createObjectURL(file);
|
|
|
+
|
|
|
+ // Создаем AudioContext с проверкой на webkit-префикс
|
|
|
+ const AudioContextConstructor = window.AudioContext || window.webkitAudioContext;
|
|
|
+ if (!AudioContextConstructor) {
|
|
|
+ URL.revokeObjectURL(objectUrl);
|
|
|
+ return reject(new Error('Web Audio API is not supported in this browser'));
|
|
|
}
|
|
|
|
|
|
+ const audioContext = new AudioContextConstructor();
|
|
|
+
|
|
|
+ const fileReader = new FileReader();
|
|
|
+
|
|
|
+ fileReader.onload = async () => {
|
|
|
+ try {
|
|
|
+ const arrayBuffer = fileReader.result as ArrayBuffer;
|
|
|
+ const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
|
+ resolve(audioBuffer.duration);
|
|
|
+ } catch (error) {
|
|
|
+ reject(
|
|
|
+ new Error(
|
|
|
+ `Error decoding audio: ${error instanceof Error ? error.message : String(error)}`,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ } finally {
|
|
|
+ URL.revokeObjectURL(objectUrl);
|
|
|
+ if (audioContext.state !== 'closed') {
|
|
|
+ await audioContext.close();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ fileReader.onerror = async () => {
|
|
|
+ URL.revokeObjectURL(objectUrl);
|
|
|
+ if (audioContext.state !== 'closed') {
|
|
|
+ await audioContext.close();
|
|
|
+ }
|
|
|
+ reject(new Error('FileReader error'));
|
|
|
+ };
|
|
|
+
|
|
|
+ fileReader.readAsArrayBuffer(file);
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+//
|
|
|
+//
|
|
|
+//
|
|
|
+//
|
|
|
+export const getValidationFunc = (
|
|
|
+ validator: Validator,
|
|
|
+): ((v: unknown) => (string | true) | Promise<string | true>) | undefined => {
|
|
|
+ const { code, value } = validator;
|
|
|
+
|
|
|
+ switch (validator.code) {
|
|
|
+ case 'required':
|
|
|
+ return (v: unknown) =>
|
|
|
+ v !== undefined && v !== null && v !== '' ? true : 'Поле должно быть заполнено';
|
|
|
+
|
|
|
+ // Строковые валидаторы
|
|
|
+ case 'maxString':
|
|
|
case 'minString': {
|
|
|
- const parsedValue = z.number().parse(Number(validator.value));
|
|
|
- return (v: string) =>
|
|
|
- v.length >= parsedValue || `Поле должно содержать минимум ${parsedValue} символов`;
|
|
|
+ const parsedValue = parseValidatorValue(value);
|
|
|
+ if (parsedValue === null) {
|
|
|
+ return () => `Некорректное значение валидатора: ${value}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ return (v: unknown) => {
|
|
|
+ if (v === null) return true;
|
|
|
+ if (typeof v !== 'string') return getTypeError('string');
|
|
|
+ return code === 'maxString'
|
|
|
+ ? v.length <= parsedValue || `Максимум ${parsedValue} символов`
|
|
|
+ : v.length >= parsedValue || `Минимум ${parsedValue} символов`;
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
- case 'isMail': {
|
|
|
- return (v: string) => isEmail(v) || `Введите корректуню почту`;
|
|
|
- }
|
|
|
+ case 'isMail':
|
|
|
+ return (v: unknown) => {
|
|
|
+ if (v === null) return true;
|
|
|
+ if (typeof v !== 'string') return getTypeError('string');
|
|
|
+ return validatorjs.isEmail(v) || 'Введите корректную почту';
|
|
|
+ };
|
|
|
|
|
|
// date
|
|
|
case 'leastYearsAgo': {
|
|
|
- const parsedValue = z.number().parse(Number(validator.value));
|
|
|
- return (v: string) =>
|
|
|
- validateLeastYearsAgo(v, parsedValue) || `Должно пройти минимум ${parsedValue} лет`;
|
|
|
+ const parsedValue = parseValidatorValue(value);
|
|
|
+ if (parsedValue === null) {
|
|
|
+ return () => `Некорректное значение валидатора: ${value}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ return (v: unknown) => {
|
|
|
+ if (v === null) return true;
|
|
|
+ if (typeof v !== 'string') return getTypeError('string');
|
|
|
+ return validateLeastYearsAgo(v, parsedValue) || `Минимум ${parsedValue} лет}`;
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
- // number
|
|
|
- case 'max': {
|
|
|
- const parsedValue = z.number().parse(Number(validator.value));
|
|
|
- return (v: number) => v <= parsedValue || `Значение не должно быть больше ${parsedValue}`;
|
|
|
+ // Числовые валидаторы
|
|
|
+ case 'max':
|
|
|
+ case 'min': {
|
|
|
+ const parsedValue = parseValidatorValue(value);
|
|
|
+ if (parsedValue === null) {
|
|
|
+ return () => `Некорректное значение валидатора: ${value}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ return (v: unknown) => {
|
|
|
+ if (v === null) return true;
|
|
|
+ if (typeof v !== 'number') return getTypeError('number');
|
|
|
+ return code === 'max'
|
|
|
+ ? v <= parsedValue || `Не больше ${parsedValue}`
|
|
|
+ : v >= parsedValue || `Не меньше ${parsedValue}`;
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
- case 'min': {
|
|
|
- const parsedValue = z.number().parse(Number(validator.value));
|
|
|
- return (v: number) => v >= parsedValue || `Значение не должно быть меньше ${parsedValue}`;
|
|
|
+ // Музыка
|
|
|
+ case 'audioMaxSec': {
|
|
|
+ const parsedValue = parseValidatorValue(value);
|
|
|
+ if (parsedValue === null) {
|
|
|
+ return () => `Некорректное значение валидатора: ${value}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ return async (v: unknown) => {
|
|
|
+ if (v === null) return true;
|
|
|
+ if (!(v instanceof File)) return getTypeError('audio');
|
|
|
+ const duration = await getAudioDuration(v);
|
|
|
+ return duration <= parsedValue || `Превышен лимит длительности ${parsedValue} секунд`;
|
|
|
+ };
|
|
|
}
|
|
|
+
|
|
|
+ default:
|
|
|
+ return () => `Неизвестный валидатор: ${code}`;
|
|
|
}
|
|
|
};
|
|
|
-
|
|
|
-// import type { ValidationRule } from 'quasar';
|
|
|
-// import type { Validator } from './custom-form-types';
|
|
|
-// import { z } from 'zod';
|
|
|
-// import { isEmail } from 'validator';
|
|
|
-
|
|
|
-// export const getValidationFunc = (validator: Validator): ValidationRule | undefined => {
|
|
|
-// switch (validator.fieldTypeCode) {
|
|
|
-// case 'string':
|
|
|
-// return getStringFunc(validator as Validator & { fieldTypeCode: 'string' });
|
|
|
-// case 'number':
|
|
|
-// return getNumberFunc(validator as Validator & { fieldTypeCode: 'number' });
|
|
|
-// case 'checkbox':
|
|
|
-// return getCheckboxFunc(validator as Validator & { fieldTypeCode: 'checkbox' });
|
|
|
-// }
|
|
|
-// };
|
|
|
-
|
|
|
-// const getStringFunc = (
|
|
|
-// validator: Validator & { fieldTypeCode: 'string' },
|
|
|
-// ): ValidationRule | undefined => {
|
|
|
-// switch (validator.code) {
|
|
|
-// case 'requiredSring': {
|
|
|
-// return (v: string) => v || `Поле должно быть заполнено`;
|
|
|
-// }
|
|
|
-
|
|
|
-// case 'maxString': {
|
|
|
-// const parsedValue = z.number().parse(validator.value);
|
|
|
-// return (v: string) =>
|
|
|
-// v.length <= parsedValue || `Поле должно содержать максимум ${parsedValue} символов`;
|
|
|
-// }
|
|
|
-
|
|
|
-// case 'minString': {
|
|
|
-// const parsedValue = z.number().parse(validator.value);
|
|
|
-// return (v: string) =>
|
|
|
-// v.length >= parsedValue || `Поле должно содержать минимум ${parsedValue} символов`;
|
|
|
-// }
|
|
|
-
|
|
|
-// case 'isMail': {
|
|
|
-// return (v: string) => (v && isEmail(v)) || `Введите корректуню почту`;
|
|
|
-// }
|
|
|
-// }
|
|
|
-// };
|
|
|
-
|
|
|
-// const getNumberFunc = (
|
|
|
-// validator: Validator & { fieldTypeCode: 'number' },
|
|
|
-// ): ValidationRule | undefined => {
|
|
|
-// switch (validator.code) {
|
|
|
-// case 'max': {
|
|
|
-// const parsedValue = z.number().parse(validator.value);
|
|
|
-// return (v: number) => v <= parsedValue || `Значение не должно быть больше ${parsedValue}`;
|
|
|
-// }
|
|
|
-
|
|
|
-// case 'min': {
|
|
|
-// const parsedValue = z.number().parse(validator.value);
|
|
|
-// return (v: number) => v >= parsedValue || `Значение не должно быть меньше ${parsedValue}`;
|
|
|
-// }
|
|
|
-// }
|
|
|
-// };
|
|
|
-
|
|
|
-// const getCheckboxFunc = (
|
|
|
-// validator: Validator & { fieldTypeCode: 'checkbox' },
|
|
|
-// ): ValidationRule | undefined => {
|
|
|
-// switch (validator.code) {
|
|
|
-// case 'required':
|
|
|
-// return (v: boolean) => v || `Поле должно быть заполнено`;
|
|
|
-// }
|
|
|
-// };
|