feat: image variants settings screens
This commit is contained in:
1 parent
f1d56ef50e
commit
f567866161
42 files changed
+7192
-11
No files matched your search
@@ -0,0 +1,154 @@
|
||||
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) }
|
||||
}
|
||||
Reference in new issue
Block a user