feat: touched fields dont reset on change source image

This commit is contained in:
2026-10-04 15:51:47 +05:00
parent 08608b878a
commit 5743b9fb3f
5 changed files with 46 additions and 8 deletions
+14
View File
@@ -36,6 +36,20 @@ test("resize-png uses the source dimensions by default", async ({ page }) => {
expectNoErrors(sink);
});
test("resize-png keeps user-set size when another image is uploaded", async ({
page,
}) => {
await openTool(page, "resize-png");
await uploadImage(page, opaquePng);
const width = page.getByRole("spinbutton", { name: "Width" });
await width.fill("25");
await uploadImage(page, landscapePng);
await expect(width).toHaveValue("25");
await expect(page.getByRole("spinbutton", { name: "Height" })).toHaveValue(
"48",
);
});
test("crop-png starts with the full source area", async ({ page }) => {
const sink = trackErrors(page);
await openTool(page, "crop-png");
@@ -20,6 +20,7 @@
type ToolSchema,
} from "$lib/registry-schema";
import { downloadZip } from "$lib/zip";
import { SvelteSet } from "svelte/reactivity";
interface Props {
page: Page;
@@ -38,6 +39,9 @@
const resultKind = $derived(tool.result ?? "image");
let values = $state<Record<string, unknown>>({});
// Поля, изменённые пользователем: при загрузке нового файла им не даём
// перезаписаться source-дефолтами. Reset снимает пометку.
const touched = new SvelteSet<string>();
let source = $state<PixelImage | null>(null);
let result = $state<PixelImage | null>(null);
let fileResult = $state<FileResult | null>(null);
@@ -67,10 +71,12 @@
}
function setValue(id: string, value: unknown) {
touched.add(id);
values = { ...values, [id]: value };
}
function reset() {
touched.clear();
values = schema
? defaultSchemaParams(schema, { source: source ?? undefined })
: {};
@@ -87,7 +93,12 @@
const decoded = await decodeFile(file);
source = decoded;
if (schema) {
values = applySourceDefaults(schema, values, { source: decoded });
values = applySourceDefaults(
schema,
values,
{ source: decoded },
touched,
);
}
} catch (e) {
displayError = toDisplayError(e);
+10
View File
@@ -89,6 +89,16 @@ describe("registry-schema: source-aware dimension defaults", () => {
});
});
it("touched-поля не перезаписываются source-дефолтами", () => {
const manual = { size: { width: 5, height: 6 } };
expect(
applySourceDefaults(sourceSchema, manual, { source }, new Set(["size"])),
).toEqual({
size: { width: 5, height: 6 },
fixed: { width: 10, height: 20 },
});
});
it("uses source dimensions when sanitizing missing values", () => {
expect(sanitizeSchemaParams(sourceSchema, {}, { source })).toEqual({
size: source,
+8 -1
View File
@@ -435,15 +435,22 @@ export function defaultSchemaParams<P>(
return out;
}
/**
* Пересчитывает дефолты от нового исходника, сохраняя значения пользователя.
* Поля из touched (изменённые вручную) не перезаписываются даже если у них
* source-дефолт — иначе загрузка нового файла сносила бы настроенные размеры.
*/
export function applySourceDefaults<P>(
schema: ToolSchema<P>,
values: Record<string, unknown>,
context: SchemaContext,
touched?: ReadonlySet<string>,
): Record<keyof P, unknown> {
const defaults = defaultSchemaParams(schema, context);
for (const key of Object.keys(schema.fields) as (keyof P)[]) {
const spec = schema.fields[key].spec;
if (spec.kind === "dimension" && spec.defaultFromSource) continue;
const fromSource = spec.kind === "dimension" && spec.defaultFromSource;
if (fromSource && !touched?.has(key as string)) continue;
if (Object.prototype.hasOwnProperty.call(values, key)) {
defaults[key] = values[key as string];
}