Files
RenewCMS/web/src/lib/variants/variantForm.ts
T

155 lines
4.0 KiB
TypeScript

import { t } from '@/i18n'
import type {
ImageVariant,
NewVariantRequest,
VariantDefinition,
VariantFit,
VariantFormat,
} from '@/composables/useImageVariants'
export const NAME_MAX_LENGTH = 32
const DIMENSION_MIN = 1
const DIMENSION_MAX = 8000
export const QUALITY_MIN = 1
export const QUALITY_MAX = 100
const DEFAULT_QUALITY = 80
const NAME_PATTERN = /^[a-z0-9-]{2,32}$/
const WHOLE_NUMBER = /^\d+$/
export type FieldName = 'name' | 'width' | 'height' | 'quality'
export interface VariantFormValues {
name: string
width: string
height: string
fit: VariantFit
format: VariantFormat
quality: string
generateOnUpload: boolean
enabled: boolean
}
export type FieldErrors = Partial<Record<FieldName, string>> & { dimension?: string }
export const FIELD_ORDER: FieldName[] = ['name', 'width', 'height', 'quality']
export const VARIANT_TEMPLATES = [
{ name: 'thumb', width: 400, height: 300, quality: 80 },
{ name: 'hero', width: 1200, height: 630, quality: 82 },
] as const
export function blankValues(): VariantFormValues {
return {
name: '',
width: '',
height: '',
fit: 'cover',
format: 'original',
quality: String(DEFAULT_QUALITY),
generateOnUpload: false,
enabled: true,
}
}
export function valuesFromTemplate(
template: (typeof VARIANT_TEMPLATES)[number],
): VariantFormValues {
return {
...blankValues(),
name: template.name,
width: String(template.width),
height: String(template.height),
format: 'webp',
quality: String(template.quality),
}
}
export function valuesFromVariant(variant: ImageVariant): VariantFormValues {
return {
name: variant.name,
width: variant.width === null ? '' : String(variant.width),
height: variant.height === null ? '' : String(variant.height),
fit: variant.fit,
format: variant.format,
quality: String(variant.quality ?? DEFAULT_QUALITY),
generateOnUpload: variant.generate_on_upload,
enabled: variant.enabled,
}
}
export function hasChanged(current: VariantFormValues, initial: VariantFormValues) {
return (Object.keys(initial) as (keyof VariantFormValues)[]).some(
(key) => current[key] !== initial[key],
)
}
export function usesQuality(format: VariantFormat) {
return format !== 'png'
}
function wholeNumberInRange(text: string, min: number, max: number) {
const trimmed = text.trim()
if (!WHOLE_NUMBER.test(trimmed)) {
return null
}
const value = Number(trimmed)
return value >= min && value <= max ? value : null
}
export function parseDimension(text: string) {
return wholeNumberInRange(text, DIMENSION_MIN, DIMENSION_MAX)
}
function parseQuality(text: string) {
return wholeNumberInRange(text, QUALITY_MIN, QUALITY_MAX)
}
export function validateField(field: FieldName, values: VariantFormValues): string | undefined {
if (field === 'name') {
return NAME_PATTERN.test(values.name)
? undefined
: t('Use lowercase letters, numbers and dashes, 2 to 32 characters.')
}
if (field === 'quality') {
return !usesQuality(values.format) || parseQuality(values.quality) !== null
? undefined
: t('Enter a value from 1 to 100.')
}
const text = values[field]
return text.trim() === '' || parseDimension(text) !== null
? undefined
: t('Enter a value from 1 to 8000.')
}
export function lacksDimension(values: VariantFormValues) {
return values.width.trim() === '' && values.height.trim() === ''
}
export function sliderValue(text: string) {
const parsed = Number.parseInt(text, 10)
if (Number.isNaN(parsed)) {
return QUALITY_MIN
}
return Math.min(QUALITY_MAX, Math.max(QUALITY_MIN, parsed))
}
export function toDefinition(values: VariantFormValues): VariantDefinition {
const definition: VariantDefinition = {
width: parseDimension(values.width),
height: parseDimension(values.height),
fit: values.fit,
format: values.format,
generate_on_upload: values.generateOnUpload,
enabled: values.enabled,
}
if (usesQuality(values.format)) {
definition.quality = parseQuality(values.quality) ?? DEFAULT_QUALITY
}
return definition
}
export function toNewVariant(values: VariantFormValues): NewVariantRequest {
return { name: values.name, ...toDefinition(values) }
}