From ca0eaa83e8886ce1b8dd0f79cdb6862986ed5658 Mon Sep 17 00:00:00 2001 From: Igor Zinken <730069+igorski@users.noreply.github.com> Date: Fri, 20 Mar 2026 19:00:34 +0100 Subject: [PATCH] Add rel to Layer structure --- src/definitions/document.ts | 8 ++++- src/factories/layer-factory.ts | 21 +++++++++--- tests/unit/factories/layer-factory.spec.ts | 37 ++++++++++++++++++++-- 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/src/definitions/document.ts b/src/definitions/document.ts index 7d1c148..a826224 100644 --- a/src/definitions/document.ts +++ b/src/definitions/document.ts @@ -1,7 +1,7 @@ /** * The MIT License (MIT) * - * Igor Zinken 2020-2025 - https://www.igorski.nl + * Igor Zinken 2020-2026 - https://www.igorski.nl * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -24,6 +24,11 @@ import type { Point } from "zcanvas"; import type { BlendModes } from "./blend-modes"; import type { LayerTypes } from "./layer-types"; +export type LayerRel = { + type: "none" | "group" | "tile"; + id?: string; +}; + export type Layer = { id: string; name: string; @@ -41,6 +46,7 @@ export type Layer = { transform: Transform; filters: Filters; text: Text; + rel: LayerRel; }; export type Transform = { diff --git a/src/factories/layer-factory.ts b/src/factories/layer-factory.ts index fba9d73..552f2ef 100644 --- a/src/factories/layer-factory.ts +++ b/src/factories/layer-factory.ts @@ -1,7 +1,7 @@ /** * The MIT License (MIT) * - * Igor Zinken 2020-2025 - https://www.igorski.nl + * Igor Zinken 2020-2026 - https://www.igorski.nl * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -22,7 +22,7 @@ */ import type { Rectangle } from "zcanvas"; import { LayerTypes, DEFAULT_LAYER_NAME, DEFAULT_TEXT_LAYER_NAME } from "@/definitions/layer-types"; -import type { Layer } from "@/definitions/document"; +import type { Layer, LayerRel } from "@/definitions/document"; import { imageToBase64, base64toCanvas } from "@/utils/canvas-util"; import TransformFactory, { type TransformProps } from "@/factories/transform-factory"; import FiltersFactory from "@/factories/filters-factory"; @@ -32,10 +32,11 @@ import type { TextProps } from "@/factories/text-factory"; let UID_COUNTER = 0; -export type LayerProps = Partial> & { +export type LayerProps = Partial> & { transform?: TransformProps; filters?: FiltersProps; text?: TextProps; + rel?: Partial; }; const LayerFactory = { @@ -46,7 +47,7 @@ const LayerFactory = { name = DEFAULT_LAYER_NAME, type = LayerTypes.LAYER_GRAPHIC, transparent = true, source = null, mask = null, left = 0, top = 0, maskX = 0, maskY = 0, width = 1, height = 1, visible = true, - transform = {}, filters = {}, text = {} + transform = {}, filters = {}, text = {}, rel = {}, }: LayerProps = {}): Layer { return { id: `layer_${( ++UID_COUNTER )}`, @@ -65,6 +66,10 @@ const LayerFactory = { text: TextFactory.create( text ), transform: TransformFactory.create( transform ), filters: FiltersFactory.create( filters ), + rel: { + type: "none", + ...rel, + }, } }, @@ -89,6 +94,10 @@ const LayerFactory = { f: TransformFactory.serialize( layer.transform ), fl: FiltersFactory.serialize( layer.filters ), v: layer.visible, + r: { + t: layer.rel.type, + i: layer.rel.id, + }, }; }, @@ -116,6 +125,10 @@ const LayerFactory = { text, transform: TransformFactory.deserialize( layer.f ), filters: FiltersFactory.deserialize( layer.fl ), + rel: layer.r ? { + type: layer.r.t, + id: layer.r.i, + } : {} }); } }; diff --git a/tests/unit/factories/layer-factory.spec.ts b/tests/unit/factories/layer-factory.spec.ts index d655179..402e09a 100644 --- a/tests/unit/factories/layer-factory.spec.ts +++ b/tests/unit/factories/layer-factory.spec.ts @@ -45,6 +45,9 @@ describe( "Layer factory", () => { text: mockText, transform: mockTransform, filters: mockFilters, + rel: { + type: "none", + }, }); }); @@ -73,7 +76,11 @@ describe( "Layer factory", () => { visible: false, text: { value: "Lorem ipsum" }, transform: { rotation: 270 }, - filters: { contrast: .7 } + filters: { contrast: .7 }, + rel: { + type: "group", + id: "Foo", + }, }); expect( layer ).toEqual({ id: expect.any( String ), @@ -92,7 +99,11 @@ describe( "Layer factory", () => { text: { value: "Lorem ipsum" }, transform: { rotation: 270 }, filters: { contrast: .7 }, - }) + rel: { + type: "group", + id: "Foo", + }, + }); }); describe( "and providing a name to the Layer", () => { @@ -131,7 +142,11 @@ describe( "Layer factory", () => { visible: false, text: { value: "Lorem ipsum" }, transform: { rotation: 270 }, - filters: { contrast: .7 } + filters: { contrast: .7 }, + rel: { + type: "tile", + id: "1", + }, }); const serializeTextSpy = vi.spyOn( TextFactory, "serialize" ).mockImplementation( args => args ); const deserializeTextSpy = vi.spyOn( TextFactory, "deserialize" ).mockImplementation( args => args ); @@ -160,6 +175,11 @@ describe( "Layer factory", () => { expect( deserializeTransformSpy ).toHaveBeenCalledWith( layer.transform ); expect( deserializeFiltersSpy ).toHaveBeenCalledWith( layer.filters ); + expect( deserialized.rel ).toEqual({ + type: "tile", + id: "1", + }); + // note id's are unique per created session instance and therefor will differ expect({ ...deserialized, @@ -169,6 +189,17 @@ describe( "Layer factory", () => { id: expect.any( String ), }); }); + + it( "should deserialize with a default rel structure for legacy serialized layers", async () => { + const serialized = LayerFactory.serialize( LayerFactory.create()); + delete serialized.r; + + const deserialized = await LayerFactory.deserialize( serialized ); + + expect( deserialized.rel ).toEqual({ + type: "none", + }); + }); }); it( "should be able to return a rectangle for a Layer", () => {