feat: add crop and canvas resize workflows
This commit is contained in:
@@ -33,6 +33,15 @@ describe("project format", () => {
|
||||
expect(() => serializeProject(missingAsset)).toThrow("references missing asset");
|
||||
});
|
||||
|
||||
test("rejects invalid persisted layer crops", () => {
|
||||
const document = projectDocument();
|
||||
const layer = document.artboards[0]!.layers[0]!;
|
||||
if (layer.type === "group") throw new Error("Expected image layer");
|
||||
layer.sourceRect = { x: 0, y: 0, w: 0, h: 10 };
|
||||
const source = JSON.stringify({ format: "image-studio-project", version: 1, savedAt: "2026-07-10T12:00:00.000Z", document });
|
||||
expect(() => parseProject(source)).toThrow("invalid source crop");
|
||||
});
|
||||
|
||||
test("creates safe project file names", () => {
|
||||
expect(projectFileName(" Summer / Study ")).toBe("Summer-Study.image-studio.json");
|
||||
expect(projectFileName("***")).toBe("untitled.image-studio.json");
|
||||
@@ -52,7 +61,7 @@ function projectDocument(): ImageDocument {
|
||||
backgroundColor: "transparent",
|
||||
visible: true,
|
||||
locked: false,
|
||||
layers: [{ id: "layer-1", type: "image", name: "Pixels", visible: true, locked: false, opacity: 1, assetId: "asset-1", transform: { position: { x: 0, y: 0 }, scale: { x: 1, y: 1 }, rotation: 0 } }],
|
||||
layers: [{ id: "layer-1", type: "image", name: "Pixels", visible: true, locked: false, opacity: 1, assetId: "asset-1", sourceRect: { x: 2, y: 3, w: 6, h: 12 }, transform: { position: { x: 0, y: 0 }, scale: { x: 1, y: 1 }, rotation: 0 } }],
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ImageDocument } from "@core/document";
|
||||
import type { Layer } from "@core/layer";
|
||||
import type { Rect } from "@core/geometry";
|
||||
|
||||
export const PROJECT_FORMAT = "image-studio-project";
|
||||
export const CURRENT_PROJECT_VERSION = 1;
|
||||
@@ -112,6 +113,8 @@ function assertLayers(value: unknown[]): asserts value is Layer[] {
|
||||
assertLayers(layer.children);
|
||||
} else if ((layer.type === "image" || layer.type === "raster") && typeof layer.assetId !== "string") {
|
||||
throw new Error("A project layer is missing its asset reference.");
|
||||
} else if ((layer.type === "image" || layer.type === "raster") && layer.sourceRect !== undefined && (!isRect(layer.sourceRect) || layer.sourceRect.w < 1 || layer.sourceRect.h < 1)) {
|
||||
throw new Error("A project layer contains an invalid source crop.");
|
||||
} else if (layer.type !== "image" && layer.type !== "raster") {
|
||||
throw new Error(`Unsupported layer type: ${layer.type}.`);
|
||||
}
|
||||
@@ -137,7 +140,7 @@ function isSize(value: unknown): boolean {
|
||||
return isRecord(value) && isFiniteNumber(value.w) && isFiniteNumber(value.h) && value.w >= 0 && value.h >= 0;
|
||||
}
|
||||
|
||||
function isRect(value: unknown): boolean {
|
||||
function isRect(value: unknown): value is Rect {
|
||||
return isRecord(value) && isFiniteNumber(value.x) && isFiniteNumber(value.y) && isFiniteNumber(value.w) && isFiniteNumber(value.h);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user