feat(web): вкладка «Редактирование» с инструментом «Свет»

В правой панели фото появилась вкладка «Редактирование» с инструментом «Свет»:
ползунки экспозиции, контраста, светов, теней, белых и чёрных и точечная кривая
с выбором канала. Правки сохраняются автоматически, переключатель «Сброс»
удаляет параметры правок и отредактированную версию фото.

Редактирование неразрушающее: параметры хранятся в метаданных фото, а
отредактированная версия рендерится в фоне отдельным объектом (edit, edit_thumb,
edit_medium, edit_preview) рядом с исходными вариантами. Оригинал не меняется,
скачивание отдаёт исходный файл. Сетка, просмотр и обложки альбомов показывают
отредактированную версию, когда она отрендерена.
This commit is contained in:
iu
2026-10-20 23:06:24 +03:00
parent afaaaab5f0
commit 0548b1ab5e
32 changed files with 1954 additions and 24 deletions
+58
View File
@@ -0,0 +1,58 @@
import { describe, expect, it } from 'vitest';
import {
emptyEditParams,
isEmptyEditParams,
normalizeEditParams,
readEditParams
} from './edit';
import type { Photo } from './types';
describe('параметры правок', () => {
it('зажимает значения в допустимые диапазоны', () => {
const params = normalizeEditParams({
light: { ...emptyEditParams().light, exposure: 99, contrast: -500 }
});
expect(params.light.exposure).toBe(5);
expect(params.light.contrast).toBe(-100);
});
it('считает пустыми нулевые параметры и линейную кривую', () => {
expect(isEmptyEditParams(emptyEditParams())).toBe(true);
const linear = normalizeEditParams({
light: {
...emptyEditParams().light,
curve: { rgb: [{ x: 0, y: 0 }, { x: 1, y: 1 }] }
}
});
expect(isEmptyEditParams(linear)).toBe(true);
const bent = normalizeEditParams({
light: {
...emptyEditParams().light,
curve: { rgb: [{ x: 0, y: 0 }, { x: 1, y: 0.5 }] }
}
});
expect(isEmptyEditParams(bent)).toBe(false);
});
it('читает параметры из метаданных фото', () => {
const photo = {
edit_params: { light: { exposure: 0.5, curve: { rgb: [{ x: 0, y: 0 }] } } }
} as unknown as Photo;
const params = readEditParams(photo);
expect(params.light.exposure).toBe(0.5);
expect(params.light.contrast).toBe(0);
});
it('сортирует точки кривой и убирает дубликаты', () => {
const params = normalizeEditParams({
light: {
...emptyEditParams().light,
curve: { rgb: [{ x: 0.8, y: 0.9 }, { x: 0.2, y: 0.1 }, { x: 0.8, y: 0.7 }] }
}
});
const points = params.light.curve.rgb ?? [];
expect(points.length).toBe(2);
expect(points[0].x).toBeCloseTo(0.2);
expect(points[1].x).toBeCloseTo(0.8);
});
});