@pooder/kit 6.2.2 → 6.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,30 +24,43 @@ function createImageCommands(tool) {
24
24
  },
25
25
  },
26
26
  {
27
- command: "getWorkingImages",
28
- id: "getWorkingImages",
29
- title: "Get Working Images",
27
+ command: "applyImageOperation",
28
+ id: "applyImageOperation",
29
+ title: "Apply Image Operation",
30
+ handler: async (id, operation, options = {}) => {
31
+ await tool.applyImageOperation(id, operation, options);
32
+ },
33
+ },
34
+ {
35
+ command: "getImageViewState",
36
+ id: "getImageViewState",
37
+ title: "Get Image View State",
30
38
  handler: () => {
31
- return tool.cloneItems(tool.workingItems);
39
+ return tool.getImageViewState();
32
40
  },
33
41
  },
34
42
  {
35
- command: "setWorkingImage",
36
- id: "setWorkingImage",
37
- title: "Set Working Image",
38
- handler: (id, updates) => {
39
- tool.updateImageInWorking(id, updates);
43
+ command: "setImageTransform",
44
+ id: "setImageTransform",
45
+ title: "Set Image Transform",
46
+ handler: async (id, updates, options = {}) => {
47
+ await tool.setImageTransform(id, updates, options);
40
48
  },
41
49
  },
42
50
  {
43
- command: "resetWorkingImages",
44
- id: "resetWorkingImages",
45
- title: "Reset Working Images",
51
+ command: "imageSessionReset",
52
+ id: "imageSessionReset",
53
+ title: "Reset Image Session",
46
54
  handler: () => {
47
- tool.workingItems = tool.cloneItems(tool.items);
48
- tool.hasWorkingChanges = false;
49
- tool.updateImages();
50
- tool.emitWorkingChange();
55
+ tool.resetImageSession();
56
+ },
57
+ },
58
+ {
59
+ command: "validateImageSession",
60
+ id: "validateImageSession",
61
+ title: "Validate Image Session",
62
+ handler: async () => {
63
+ return await tool.validateImageSession();
51
64
  },
52
65
  },
53
66
  {
@@ -55,7 +68,7 @@ function createImageCommands(tool) {
55
68
  id: "completeImages",
56
69
  title: "Complete Images",
57
70
  handler: async () => {
58
- return await tool.commitWorkingImagesAsCropped();
71
+ return await tool.completeImageSession();
59
72
  },
60
73
  },
61
74
  {
@@ -66,22 +79,6 @@ function createImageCommands(tool) {
66
79
  return await tool.exportUserCroppedImage(options);
67
80
  },
68
81
  },
69
- {
70
- command: "fitImageToArea",
71
- id: "fitImageToArea",
72
- title: "Fit Image to Area",
73
- handler: async (id, area) => {
74
- await tool.fitImageToArea(id, area);
75
- },
76
- },
77
- {
78
- command: "fitImageToDefaultArea",
79
- id: "fitImageToDefaultArea",
80
- title: "Fit Image to Default Area",
81
- handler: async (id) => {
82
- await tool.fitImageToDefaultArea(id);
83
- },
84
- },
85
82
  {
86
83
  command: "focusImage",
87
84
  id: "focusImage",
@@ -95,9 +92,10 @@ function createImageCommands(tool) {
95
92
  id: "removeImage",
96
93
  title: "Remove Image",
97
94
  handler: (id) => {
98
- const removed = tool.items.find((item) => item.id === id);
99
- const next = tool.items.filter((item) => item.id !== id);
100
- if (next.length !== tool.items.length) {
95
+ const sourceItems = tool.isToolActive ? tool.workingItems : tool.items;
96
+ const removed = sourceItems.find((item) => item.id === id);
97
+ const next = sourceItems.filter((item) => item.id !== id);
98
+ if (next.length !== sourceItems.length) {
101
99
  tool.purgeSourceSizeCacheForItem(removed);
102
100
  if (tool.focusedImageId === id) {
103
101
  tool.setImageFocus(null, {
@@ -105,6 +103,13 @@ function createImageCommands(tool) {
105
103
  skipRender: true,
106
104
  });
107
105
  }
106
+ if (tool.isToolActive) {
107
+ tool.workingItems = tool.cloneItems(next);
108
+ tool.hasWorkingChanges = true;
109
+ tool.updateImages();
110
+ tool.emitWorkingChange(id);
111
+ return;
112
+ }
108
113
  tool.updateConfig(next);
109
114
  }
110
115
  },
@@ -127,6 +132,13 @@ function createImageCommands(tool) {
127
132
  syncCanvasSelection: true,
128
133
  skipRender: true,
129
134
  });
135
+ if (tool.isToolActive) {
136
+ tool.workingItems = [];
137
+ tool.hasWorkingChanges = true;
138
+ tool.updateImages();
139
+ tool.emitWorkingChange();
140
+ return;
141
+ }
130
142
  tool.updateConfig([]);
131
143
  },
132
144
  },
@@ -135,11 +147,19 @@ function createImageCommands(tool) {
135
147
  id: "bringToFront",
136
148
  title: "Bring Image to Front",
137
149
  handler: (id) => {
138
- const index = tool.items.findIndex((item) => item.id === id);
139
- if (index !== -1 && index < tool.items.length - 1) {
140
- const next = [...tool.items];
150
+ const sourceItems = tool.isToolActive ? tool.workingItems : tool.items;
151
+ const index = sourceItems.findIndex((item) => item.id === id);
152
+ if (index !== -1 && index < sourceItems.length - 1) {
153
+ const next = [...sourceItems];
141
154
  const [item] = next.splice(index, 1);
142
155
  next.push(item);
156
+ if (tool.isToolActive) {
157
+ tool.workingItems = tool.cloneItems(next);
158
+ tool.hasWorkingChanges = true;
159
+ tool.updateImages();
160
+ tool.emitWorkingChange(id);
161
+ return;
162
+ }
143
163
  tool.updateConfig(next);
144
164
  }
145
165
  },
@@ -149,11 +169,19 @@ function createImageCommands(tool) {
149
169
  id: "sendToBack",
150
170
  title: "Send Image to Back",
151
171
  handler: (id) => {
152
- const index = tool.items.findIndex((item) => item.id === id);
172
+ const sourceItems = tool.isToolActive ? tool.workingItems : tool.items;
173
+ const index = sourceItems.findIndex((item) => item.id === id);
153
174
  if (index > 0) {
154
- const next = [...tool.items];
175
+ const next = [...sourceItems];
155
176
  const [item] = next.splice(index, 1);
156
177
  next.unshift(item);
178
+ if (tool.isToolActive) {
179
+ tool.workingItems = tool.cloneItems(next);
180
+ tool.hasWorkingChanges = true;
181
+ tool.updateImages();
182
+ tool.emitWorkingChange(id);
183
+ return;
184
+ }
157
185
  tool.updateConfig(next);
158
186
  }
159
187
  },
@@ -125,5 +125,12 @@ function createImageConfigurations() {
125
125
  label: "Image Frame Outer Background",
126
126
  default: "#f5f5f5",
127
127
  },
128
+ {
129
+ id: "image.session.placementPolicy",
130
+ type: "select",
131
+ label: "Image Session Placement Policy",
132
+ options: ["free", "warn", "strict"],
133
+ default: "free",
134
+ },
128
135
  ];
129
136
  }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveImageOperationArea = resolveImageOperationArea;
4
+ exports.computeImageOperationUpdates = computeImageOperationUpdates;
5
+ const sourceSizeCache_1 = require("../../shared/imaging/sourceSizeCache");
6
+ function clampNormalizedAnchor(value) {
7
+ return Math.max(-1, Math.min(2, value));
8
+ }
9
+ function toNormalizedAnchor(center, start, size) {
10
+ return clampNormalizedAnchor((center - start) / Math.max(1, size));
11
+ }
12
+ function resolveAbsoluteScale(operation, area, source) {
13
+ const widthScale = Math.max(1, area.width) / Math.max(1, source.width);
14
+ const heightScale = Math.max(1, area.height) / Math.max(1, source.height);
15
+ switch (operation.type) {
16
+ case "cover":
17
+ return Math.max(widthScale, heightScale);
18
+ case "contain":
19
+ return Math.min(widthScale, heightScale);
20
+ case "maximizeWidth":
21
+ return widthScale;
22
+ case "maximizeHeight":
23
+ return heightScale;
24
+ default:
25
+ return null;
26
+ }
27
+ }
28
+ function resolveImageOperationArea(args) {
29
+ const spec = args.area || { type: "frame" };
30
+ if (spec.type === "custom") {
31
+ return {
32
+ width: Math.max(1, spec.width),
33
+ height: Math.max(1, spec.height),
34
+ centerX: spec.centerX,
35
+ centerY: spec.centerY,
36
+ };
37
+ }
38
+ if (spec.type === "viewport") {
39
+ return {
40
+ width: Math.max(1, args.viewport.width),
41
+ height: Math.max(1, args.viewport.height),
42
+ centerX: args.viewport.left + args.viewport.width / 2,
43
+ centerY: args.viewport.top + args.viewport.height / 2,
44
+ };
45
+ }
46
+ return {
47
+ width: Math.max(1, args.frame.width),
48
+ height: Math.max(1, args.frame.height),
49
+ centerX: args.frame.left + args.frame.width / 2,
50
+ centerY: args.frame.top + args.frame.height / 2,
51
+ };
52
+ }
53
+ function computeImageOperationUpdates(args) {
54
+ const { frame, source, operation, area } = args;
55
+ if (operation.type === "resetTransform") {
56
+ return {
57
+ scale: 1,
58
+ left: 0.5,
59
+ top: 0.5,
60
+ angle: 0,
61
+ };
62
+ }
63
+ const left = toNormalizedAnchor(area.centerX, frame.left, frame.width);
64
+ const top = toNormalizedAnchor(area.centerY, frame.top, frame.height);
65
+ if (operation.type === "center") {
66
+ return { left, top };
67
+ }
68
+ const absoluteScale = resolveAbsoluteScale(operation, area, source);
69
+ const coverScale = (0, sourceSizeCache_1.getCoverScale)(frame, source);
70
+ return {
71
+ scale: Math.max(0.05, (absoluteScale || coverScale) / coverScale),
72
+ left,
73
+ top,
74
+ };
75
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateImagePlacement = validateImagePlacement;
4
+ const sourceSizeCache_1 = require("../../shared/imaging/sourceSizeCache");
5
+ function toRadians(angle) {
6
+ return (angle * Math.PI) / 180;
7
+ }
8
+ function validateImagePlacement(args) {
9
+ const { frame, source, placement } = args;
10
+ if (frame.width <= 0 ||
11
+ frame.height <= 0 ||
12
+ source.width <= 0 ||
13
+ source.height <= 0) {
14
+ return { ok: true };
15
+ }
16
+ const coverScale = (0, sourceSizeCache_1.getCoverScale)(frame, source);
17
+ const imageWidth = source.width * coverScale * Math.max(0.05, Number(placement.scale || 1));
18
+ const imageHeight = source.height * coverScale * Math.max(0.05, Number(placement.scale || 1));
19
+ if (imageWidth <= 0 || imageHeight <= 0) {
20
+ return { ok: true };
21
+ }
22
+ const centerX = frame.left + placement.left * frame.width;
23
+ const centerY = frame.top + placement.top * frame.height;
24
+ const halfWidth = imageWidth / 2;
25
+ const halfHeight = imageHeight / 2;
26
+ const radians = toRadians(placement.angle || 0);
27
+ const cos = Math.cos(radians);
28
+ const sin = Math.sin(radians);
29
+ const frameCorners = [
30
+ { x: frame.left, y: frame.top },
31
+ { x: frame.left + frame.width, y: frame.top },
32
+ { x: frame.left + frame.width, y: frame.top + frame.height },
33
+ { x: frame.left, y: frame.top + frame.height },
34
+ ];
35
+ const coversFrame = frameCorners.every((corner) => {
36
+ const dx = corner.x - centerX;
37
+ const dy = corner.y - centerY;
38
+ const localX = dx * cos + dy * sin;
39
+ const localY = -dx * sin + dy * cos;
40
+ return (Math.abs(localX) <= halfWidth + 1e-6 &&
41
+ Math.abs(localY) <= halfHeight + 1e-6);
42
+ });
43
+ return { ok: coversFrame };
44
+ }
@@ -17,5 +17,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./ImageTool"), exports);
18
18
  __exportStar(require("./commands"), exports);
19
19
  __exportStar(require("./config"), exports);
20
+ __exportStar(require("./imageOperations"), exports);
20
21
  __exportStar(require("./model"), exports);
21
22
  __exportStar(require("./renderer"), exports);
@@ -1,2 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hasAnyImageInViewState = hasAnyImageInViewState;
4
+ function hasAnyImageInViewState(state) {
5
+ return Boolean(state?.hasAnyImage);
6
+ }
@@ -12,6 +12,7 @@ const config_2 = require("../src/extensions/white-ink/config");
12
12
  const commands_3 = require("../src/extensions/dieline/commands");
13
13
  const config_3 = require("../src/extensions/dieline/config");
14
14
  const featureCoordinates_1 = require("../src/extensions/featureCoordinates");
15
+ const model_1 = require("../src/extensions/image/model");
15
16
  function assert(condition, message) {
16
17
  if (!condition)
17
18
  throw new Error(message);
@@ -66,10 +67,12 @@ function testMaskOps() {
66
67
  assert(filled[4 * 9 + 4] === 1, "hole should be filled");
67
68
  const imgW = 2;
68
69
  const imgH = 1;
69
- const rgba = new Uint8ClampedArray([
70
- 255, 255, 255, 255, 10, 10, 10, 254,
71
- ]);
72
- const imageData = { width: imgW, height: imgH, data: rgba };
70
+ const rgba = new Uint8ClampedArray([255, 255, 255, 255, 10, 10, 10, 254]);
71
+ const imageData = {
72
+ width: imgW,
73
+ height: imgH,
74
+ data: rgba,
75
+ };
73
76
  const paddedWidth = imgW + 4;
74
77
  const paddedHeight = imgH + 4;
75
78
  const created = (0, maskOps_1.createMask)(imageData, {
@@ -169,6 +172,43 @@ function testVisibilityDsl() {
169
172
  ],
170
173
  }, context) === true, "any failed");
171
174
  }
175
+ function testImageViewStateHelper() {
176
+ assert((0, model_1.hasAnyImageInViewState)(null) === false, "null image state should be empty");
177
+ assert((0, model_1.hasAnyImageInViewState)({
178
+ items: [],
179
+ hasAnyImage: false,
180
+ focusedId: null,
181
+ focusedItem: null,
182
+ isToolActive: false,
183
+ isImageSelectionActive: false,
184
+ hasWorkingChanges: false,
185
+ source: "committed",
186
+ placementPolicy: "free",
187
+ sessionNotice: null,
188
+ }) === false, "empty image state should report false");
189
+ assert((0, model_1.hasAnyImageInViewState)({
190
+ items: [
191
+ {
192
+ id: "img-1",
193
+ url: "blob:test",
194
+ opacity: 1,
195
+ },
196
+ ],
197
+ hasAnyImage: true,
198
+ focusedId: "img-1",
199
+ focusedItem: {
200
+ id: "img-1",
201
+ url: "blob:test",
202
+ opacity: 1,
203
+ },
204
+ isToolActive: true,
205
+ isImageSelectionActive: true,
206
+ hasWorkingChanges: true,
207
+ source: "working",
208
+ placementPolicy: "free",
209
+ sessionNotice: null,
210
+ }) === true, "non-empty image state should report true");
211
+ }
172
212
  function testContributionCompatibility() {
173
213
  const imageCommandNames = (0, commands_1.createImageCommands)({}).map((entry) => entry.command);
174
214
  const whiteInkCommandNames = (0, commands_2.createWhiteInkCommands)({}).map((entry) => entry.command);
@@ -179,13 +219,13 @@ function testContributionCompatibility() {
179
219
  const expectedImageCommands = [
180
220
  "addImage",
181
221
  "upsertImage",
182
- "getWorkingImages",
183
- "setWorkingImage",
184
- "resetWorkingImages",
222
+ "applyImageOperation",
223
+ "getImageViewState",
224
+ "setImageTransform",
225
+ "imageSessionReset",
226
+ "validateImageSession",
185
227
  "completeImages",
186
228
  "exportUserCroppedImage",
187
- "fitImageToArea",
188
- "fitImageToDefaultArea",
189
229
  "focusImage",
190
230
  "removeImage",
191
231
  "updateImage",
@@ -249,6 +289,7 @@ function testContributionCompatibility() {
249
289
  "image.frame.dashLength",
250
290
  "image.frame.innerBackground",
251
291
  "image.frame.outerBackground",
292
+ "image.session.placementPolicy",
252
293
  ];
253
294
  const expectedWhiteInkConfigKeys = [
254
295
  "whiteInk.items",
@@ -285,6 +326,7 @@ function main() {
285
326
  testEdgeScale();
286
327
  testFeaturePlacementProjection();
287
328
  testVisibilityDsl();
329
+ testImageViewStateHelper();
288
330
  testContributionCompatibility();
289
331
  console.log("ok");
290
332
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @pooder/kit
2
2
 
3
+ ## 6.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - image placement constraint
8
+ - Updated dependencies
9
+ - @pooder/core@2.2.2
10
+
11
+ ## 6.3.0
12
+
13
+ ### Minor Changes
14
+
15
+ - pooder facade
16
+
3
17
  ## 6.2.2
4
18
 
5
19
  ### Patch Changes