@pooder/kit 6.2.1 → 6.3.0

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,35 @@ 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();
51
56
  },
52
57
  },
53
58
  {
@@ -66,22 +71,6 @@ function createImageCommands(tool) {
66
71
  return await tool.exportUserCroppedImage(options);
67
72
  },
68
73
  },
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
74
  {
86
75
  command: "focusImage",
87
76
  id: "focusImage",
@@ -95,9 +84,10 @@ function createImageCommands(tool) {
95
84
  id: "removeImage",
96
85
  title: "Remove Image",
97
86
  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) {
87
+ const sourceItems = tool.isToolActive ? tool.workingItems : tool.items;
88
+ const removed = sourceItems.find((item) => item.id === id);
89
+ const next = sourceItems.filter((item) => item.id !== id);
90
+ if (next.length !== sourceItems.length) {
101
91
  tool.purgeSourceSizeCacheForItem(removed);
102
92
  if (tool.focusedImageId === id) {
103
93
  tool.setImageFocus(null, {
@@ -105,6 +95,13 @@ function createImageCommands(tool) {
105
95
  skipRender: true,
106
96
  });
107
97
  }
98
+ if (tool.isToolActive) {
99
+ tool.workingItems = tool.cloneItems(next);
100
+ tool.hasWorkingChanges = true;
101
+ tool.updateImages();
102
+ tool.emitWorkingChange(id);
103
+ return;
104
+ }
108
105
  tool.updateConfig(next);
109
106
  }
110
107
  },
@@ -127,6 +124,13 @@ function createImageCommands(tool) {
127
124
  syncCanvasSelection: true,
128
125
  skipRender: true,
129
126
  });
127
+ if (tool.isToolActive) {
128
+ tool.workingItems = [];
129
+ tool.hasWorkingChanges = true;
130
+ tool.updateImages();
131
+ tool.emitWorkingChange();
132
+ return;
133
+ }
130
134
  tool.updateConfig([]);
131
135
  },
132
136
  },
@@ -135,11 +139,19 @@ function createImageCommands(tool) {
135
139
  id: "bringToFront",
136
140
  title: "Bring Image to Front",
137
141
  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];
142
+ const sourceItems = tool.isToolActive ? tool.workingItems : tool.items;
143
+ const index = sourceItems.findIndex((item) => item.id === id);
144
+ if (index !== -1 && index < sourceItems.length - 1) {
145
+ const next = [...sourceItems];
141
146
  const [item] = next.splice(index, 1);
142
147
  next.push(item);
148
+ if (tool.isToolActive) {
149
+ tool.workingItems = tool.cloneItems(next);
150
+ tool.hasWorkingChanges = true;
151
+ tool.updateImages();
152
+ tool.emitWorkingChange(id);
153
+ return;
154
+ }
143
155
  tool.updateConfig(next);
144
156
  }
145
157
  },
@@ -149,11 +161,19 @@ function createImageCommands(tool) {
149
161
  id: "sendToBack",
150
162
  title: "Send Image to Back",
151
163
  handler: (id) => {
152
- const index = tool.items.findIndex((item) => item.id === id);
164
+ const sourceItems = tool.isToolActive ? tool.workingItems : tool.items;
165
+ const index = sourceItems.findIndex((item) => item.id === id);
153
166
  if (index > 0) {
154
- const next = [...tool.items];
167
+ const next = [...sourceItems];
155
168
  const [item] = next.splice(index, 1);
156
169
  next.unshift(item);
170
+ if (tool.isToolActive) {
171
+ tool.workingItems = tool.cloneItems(next);
172
+ tool.hasWorkingChanges = true;
173
+ tool.updateImages();
174
+ tool.emitWorkingChange(id);
175
+ return;
176
+ }
157
177
  tool.updateConfig(next);
158
178
  }
159
179
  },
@@ -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
+ }
@@ -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
+ }
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildImageSessionOverlaySpecs = buildImageSessionOverlaySpecs;
4
+ const geometry_1 = require("../geometry");
5
+ const EPSILON = 0.0001;
6
+ const SHAPE_OUTLINE_COLOR = "rgba(255, 0, 0, 0.9)";
7
+ const DEFAULT_HATCH_FILL = "rgba(255, 0, 0, 0.22)";
8
+ function buildRectPath(width, height) {
9
+ return `M 0 0 L ${width} 0 L ${width} ${height} L 0 ${height} Z`;
10
+ }
11
+ function buildViewportMaskPath(viewport, cutRect) {
12
+ const cutLeft = cutRect.left - viewport.left;
13
+ const cutTop = cutRect.top - viewport.top;
14
+ return [
15
+ buildRectPath(viewport.width, viewport.height),
16
+ `M ${cutLeft} ${cutTop} L ${cutLeft + cutRect.width} ${cutTop} L ${cutLeft + cutRect.width} ${cutTop + cutRect.height} L ${cutLeft} ${cutTop + cutRect.height} Z`,
17
+ ].join(" ");
18
+ }
19
+ function resolveCutShapeRadiusPx(geometry, cutRect) {
20
+ const visualRadius = Number.isFinite(geometry.radius)
21
+ ? Math.max(0, geometry.radius)
22
+ : 0;
23
+ const visualOffset = Number.isFinite(geometry.offset) ? geometry.offset : 0;
24
+ const rawCutRadius = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
25
+ const maxRadius = Math.max(0, Math.min(cutRect.width, cutRect.height) / 2);
26
+ return Math.max(0, Math.min(maxRadius, rawCutRadius));
27
+ }
28
+ function buildBuiltinShapeOverlayPaths(cutRect, geometry) {
29
+ if (!geometry || geometry.shape === "custom") {
30
+ return null;
31
+ }
32
+ const radius = resolveCutShapeRadiusPx(geometry, cutRect);
33
+ if (geometry.shape === "rect" && radius <= EPSILON) {
34
+ return null;
35
+ }
36
+ const shapePathData = (0, geometry_1.generateDielinePath)({
37
+ shape: geometry.shape,
38
+ shapeStyle: geometry.shapeStyle,
39
+ width: Math.max(1, cutRect.width),
40
+ height: Math.max(1, cutRect.height),
41
+ radius,
42
+ x: cutRect.width / 2,
43
+ y: cutRect.height / 2,
44
+ features: [],
45
+ canvasWidth: Math.max(1, cutRect.width),
46
+ canvasHeight: Math.max(1, cutRect.height),
47
+ });
48
+ if (!shapePathData) {
49
+ return null;
50
+ }
51
+ return {
52
+ shapePathData,
53
+ hatchPathData: `${buildRectPath(cutRect.width, cutRect.height)} ${shapePathData}`,
54
+ };
55
+ }
56
+ function buildImageSessionOverlaySpecs(args) {
57
+ const { viewport, layout, geometry, visual, hatchPattern } = args;
58
+ const cutRect = layout.cutRect;
59
+ const specs = [];
60
+ specs.push({
61
+ id: "image.cropMask.rect",
62
+ type: "path",
63
+ space: "screen",
64
+ data: { id: "image.cropMask.rect", zIndex: 1 },
65
+ props: {
66
+ pathData: buildViewportMaskPath(viewport, cutRect),
67
+ left: viewport.left,
68
+ top: viewport.top,
69
+ originX: "left",
70
+ originY: "top",
71
+ fill: visual.outerBackground,
72
+ stroke: null,
73
+ fillRule: "evenodd",
74
+ selectable: false,
75
+ evented: false,
76
+ excludeFromExport: true,
77
+ objectCaching: false,
78
+ },
79
+ });
80
+ const shapeOverlay = buildBuiltinShapeOverlayPaths(cutRect, geometry);
81
+ if (shapeOverlay) {
82
+ specs.push({
83
+ id: "image.cropShapeHatch",
84
+ type: "path",
85
+ space: "screen",
86
+ data: { id: "image.cropShapeHatch", zIndex: 5 },
87
+ props: {
88
+ pathData: shapeOverlay.hatchPathData,
89
+ left: cutRect.left,
90
+ top: cutRect.top,
91
+ originX: "left",
92
+ originY: "top",
93
+ fill: hatchPattern || DEFAULT_HATCH_FILL,
94
+ opacity: hatchPattern ? 1 : 0.8,
95
+ stroke: null,
96
+ fillRule: "evenodd",
97
+ selectable: false,
98
+ evented: false,
99
+ excludeFromExport: true,
100
+ objectCaching: false,
101
+ },
102
+ });
103
+ specs.push({
104
+ id: "image.cropShapeOutline",
105
+ type: "path",
106
+ space: "screen",
107
+ data: { id: "image.cropShapeOutline", zIndex: 6 },
108
+ props: {
109
+ pathData: shapeOverlay.shapePathData,
110
+ left: cutRect.left,
111
+ top: cutRect.top,
112
+ originX: "left",
113
+ originY: "top",
114
+ fill: "transparent",
115
+ stroke: SHAPE_OUTLINE_COLOR,
116
+ strokeWidth: 1,
117
+ selectable: false,
118
+ evented: false,
119
+ excludeFromExport: true,
120
+ objectCaching: false,
121
+ },
122
+ });
123
+ }
124
+ specs.push({
125
+ id: "image.cropFrame",
126
+ type: "rect",
127
+ space: "screen",
128
+ data: { id: "image.cropFrame", zIndex: 7 },
129
+ props: {
130
+ left: cutRect.left,
131
+ top: cutRect.top,
132
+ width: cutRect.width,
133
+ height: cutRect.height,
134
+ originX: "left",
135
+ originY: "top",
136
+ fill: visual.innerBackground,
137
+ stroke: visual.strokeStyle === "hidden" ? "rgba(0,0,0,0)" : visual.strokeColor,
138
+ strokeWidth: visual.strokeStyle === "hidden" ? 0 : visual.strokeWidth,
139
+ strokeDashArray: visual.strokeStyle === "dashed"
140
+ ? [visual.dashLength, visual.dashLength]
141
+ : undefined,
142
+ selectable: false,
143
+ evented: false,
144
+ excludeFromExport: true,
145
+ },
146
+ });
147
+ return specs;
148
+ }
@@ -8,7 +8,7 @@ const EXTENSION_LINE_LENGTH = 5;
8
8
  const MIN_ARROW_SIZE = 4;
9
9
  const THICKNESS_TO_STROKE_WIDTH_RATIO = 20;
10
10
  const DEFAULT_THICKNESS = 20;
11
- const DEFAULT_GAP = 45;
11
+ const DEFAULT_GAP = 65;
12
12
  const DEFAULT_FONT_SIZE = 10;
13
13
  const DEFAULT_BACKGROUND_COLOR = "#f0f0f0";
14
14
  const DEFAULT_TEXT_COLOR = "#333333";
@@ -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);
@@ -169,6 +170,39 @@ function testVisibilityDsl() {
169
170
  ],
170
171
  }, context) === true, "any failed");
171
172
  }
173
+ function testImageViewStateHelper() {
174
+ assert((0, model_1.hasAnyImageInViewState)(null) === false, "null image state should be empty");
175
+ assert((0, model_1.hasAnyImageInViewState)({
176
+ items: [],
177
+ hasAnyImage: false,
178
+ focusedId: null,
179
+ focusedItem: null,
180
+ isToolActive: false,
181
+ isImageSelectionActive: false,
182
+ hasWorkingChanges: false,
183
+ source: "committed",
184
+ }) === false, "empty image state should report false");
185
+ assert((0, model_1.hasAnyImageInViewState)({
186
+ items: [
187
+ {
188
+ id: "img-1",
189
+ url: "blob:test",
190
+ opacity: 1,
191
+ },
192
+ ],
193
+ hasAnyImage: true,
194
+ focusedId: "img-1",
195
+ focusedItem: {
196
+ id: "img-1",
197
+ url: "blob:test",
198
+ opacity: 1,
199
+ },
200
+ isToolActive: true,
201
+ isImageSelectionActive: true,
202
+ hasWorkingChanges: true,
203
+ source: "working",
204
+ }) === true, "non-empty image state should report true");
205
+ }
172
206
  function testContributionCompatibility() {
173
207
  const imageCommandNames = (0, commands_1.createImageCommands)({}).map((entry) => entry.command);
174
208
  const whiteInkCommandNames = (0, commands_2.createWhiteInkCommands)({}).map((entry) => entry.command);
@@ -179,13 +213,12 @@ function testContributionCompatibility() {
179
213
  const expectedImageCommands = [
180
214
  "addImage",
181
215
  "upsertImage",
182
- "getWorkingImages",
183
- "setWorkingImage",
184
- "resetWorkingImages",
216
+ "applyImageOperation",
217
+ "getImageViewState",
218
+ "setImageTransform",
219
+ "imageSessionReset",
185
220
  "completeImages",
186
221
  "exportUserCroppedImage",
187
- "fitImageToArea",
188
- "fitImageToDefaultArea",
189
222
  "focusImage",
190
223
  "removeImage",
191
224
  "updateImage",
@@ -285,6 +318,7 @@ function main() {
285
318
  testEdgeScale();
286
319
  testFeaturePlacementProjection();
287
320
  testVisibilityDsl();
321
+ testImageViewStateHelper();
288
322
  testContributionCompatibility();
289
323
  console.log("ok");
290
324
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @pooder/kit
2
2
 
3
+ ## 6.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - pooder facade
8
+
9
+ ## 6.2.2
10
+
11
+ ### Patch Changes
12
+
13
+ - bugfix
14
+
3
15
  ## 6.2.1
4
16
 
5
17
  ### Patch Changes