@pooder/kit 6.3.0 → 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.
package/dist/index.js CHANGED
@@ -2383,12 +2383,20 @@ function createImageCommands(tool) {
2383
2383
  tool.resetImageSession();
2384
2384
  }
2385
2385
  },
2386
+ {
2387
+ command: "validateImageSession",
2388
+ id: "validateImageSession",
2389
+ title: "Validate Image Session",
2390
+ handler: async () => {
2391
+ return await tool.validateImageSession();
2392
+ }
2393
+ },
2386
2394
  {
2387
2395
  command: "completeImages",
2388
2396
  id: "completeImages",
2389
2397
  title: "Complete Images",
2390
2398
  handler: async () => {
2391
- return await tool.commitWorkingImagesAsCropped();
2399
+ return await tool.completeImageSession();
2392
2400
  }
2393
2401
  },
2394
2402
  {
@@ -2633,6 +2641,13 @@ function createImageConfigurations() {
2633
2641
  type: "color",
2634
2642
  label: "Image Frame Outer Background",
2635
2643
  default: "#f5f5f5"
2644
+ },
2645
+ {
2646
+ id: "image.session.placementPolicy",
2647
+ type: "select",
2648
+ label: "Image Session Placement Policy",
2649
+ options: ["free", "warn", "strict"],
2650
+ default: "free"
2636
2651
  }
2637
2652
  ];
2638
2653
  }
@@ -2709,6 +2724,44 @@ function computeImageOperationUpdates(args) {
2709
2724
  };
2710
2725
  }
2711
2726
 
2727
+ // src/extensions/image/imagePlacement.ts
2728
+ function toRadians(angle) {
2729
+ return angle * Math.PI / 180;
2730
+ }
2731
+ function validateImagePlacement(args) {
2732
+ const { frame, source, placement } = args;
2733
+ if (frame.width <= 0 || frame.height <= 0 || source.width <= 0 || source.height <= 0) {
2734
+ return { ok: true };
2735
+ }
2736
+ const coverScale = getCoverScale(frame, source);
2737
+ const imageWidth = source.width * coverScale * Math.max(0.05, Number(placement.scale || 1));
2738
+ const imageHeight = source.height * coverScale * Math.max(0.05, Number(placement.scale || 1));
2739
+ if (imageWidth <= 0 || imageHeight <= 0) {
2740
+ return { ok: true };
2741
+ }
2742
+ const centerX = frame.left + placement.left * frame.width;
2743
+ const centerY = frame.top + placement.top * frame.height;
2744
+ const halfWidth = imageWidth / 2;
2745
+ const halfHeight = imageHeight / 2;
2746
+ const radians = toRadians(placement.angle || 0);
2747
+ const cos = Math.cos(radians);
2748
+ const sin = Math.sin(radians);
2749
+ const frameCorners = [
2750
+ { x: frame.left, y: frame.top },
2751
+ { x: frame.left + frame.width, y: frame.top },
2752
+ { x: frame.left + frame.width, y: frame.top + frame.height },
2753
+ { x: frame.left, y: frame.top + frame.height }
2754
+ ];
2755
+ const coversFrame = frameCorners.every((corner) => {
2756
+ const dx = corner.x - centerX;
2757
+ const dy = corner.y - centerY;
2758
+ const localX = dx * cos + dy * sin;
2759
+ const localY = -dx * sin + dy * cos;
2760
+ return Math.abs(localX) <= halfWidth + 1e-6 && Math.abs(localY) <= halfHeight + 1e-6;
2761
+ });
2762
+ return { ok: coversFrame };
2763
+ }
2764
+
2712
2765
  // src/extensions/geometry.ts
2713
2766
  var import_paper = __toESM(require("paper"));
2714
2767
 
@@ -3511,6 +3564,7 @@ var ImageTool = class {
3511
3564
  this.activeSnapX = null;
3512
3565
  this.activeSnapY = null;
3513
3566
  this.movingImageId = null;
3567
+ this.sessionNotice = null;
3514
3568
  this.hasRenderedSnapGuides = false;
3515
3569
  this.subscriptions = new SubscriptionBag();
3516
3570
  this.imageControlsByCapabilityKey = /* @__PURE__ */ new Map();
@@ -3710,7 +3764,10 @@ var ImageTool = class {
3710
3764
  this.updateImages();
3711
3765
  return;
3712
3766
  }
3713
- if (e.key.startsWith("size.") || e.key.startsWith("image.frame.") || e.key.startsWith("image.control.")) {
3767
+ if (e.key.startsWith("size.") || e.key.startsWith("image.frame.") || e.key.startsWith("image.session.") || e.key.startsWith("image.control.")) {
3768
+ if (e.key === "image.session.placementPolicy") {
3769
+ this.clearSessionNotice();
3770
+ }
3714
3771
  if (e.key.startsWith("image.control.")) {
3715
3772
  this.imageControlsByCapabilityKey.clear();
3716
3773
  }
@@ -4154,6 +4211,7 @@ var ImageTool = class {
4154
4211
  interaction: "session",
4155
4212
  commands: {
4156
4213
  begin: "imageSessionReset",
4214
+ validate: "validateImageSession",
4157
4215
  commit: "completeImages",
4158
4216
  rollback: "imageSessionReset"
4159
4217
  },
@@ -4192,6 +4250,32 @@ var ImageTool = class {
4192
4250
  getViewItems() {
4193
4251
  return this.isToolActive ? this.workingItems : this.items;
4194
4252
  }
4253
+ getPlacementPolicy() {
4254
+ const policy = this.getConfig(
4255
+ "image.session.placementPolicy",
4256
+ "free"
4257
+ );
4258
+ return policy === "warn" || policy === "strict" ? policy : "free";
4259
+ }
4260
+ areSessionNoticesEqual(a, b) {
4261
+ if (!a && !b) return true;
4262
+ if (!a || !b) return false;
4263
+ return a.code === b.code && a.level === b.level && a.message === b.message && a.policy === b.policy && JSON.stringify(a.imageIds) === JSON.stringify(b.imageIds);
4264
+ }
4265
+ setSessionNotice(notice, options = {}) {
4266
+ var _a;
4267
+ if (this.areSessionNoticesEqual(this.sessionNotice, notice)) {
4268
+ return;
4269
+ }
4270
+ this.sessionNotice = notice;
4271
+ if (options.emit !== false) {
4272
+ (_a = this.context) == null ? void 0 : _a.eventBus.emit("image:session:notice", this.sessionNotice);
4273
+ this.emitImageStateChange();
4274
+ }
4275
+ }
4276
+ clearSessionNotice(options = {}) {
4277
+ this.setSessionNotice(null, options);
4278
+ }
4195
4279
  getImageViewState() {
4196
4280
  this.syncToolActiveFromWorkbench();
4197
4281
  const items = this.cloneItems(this.getViewItems());
@@ -4204,7 +4288,9 @@ var ImageTool = class {
4204
4288
  isToolActive: this.isToolActive,
4205
4289
  isImageSelectionActive: this.isImageSelectionActive,
4206
4290
  hasWorkingChanges: this.hasWorkingChanges,
4207
- source: this.isToolActive ? "working" : "committed"
4291
+ source: this.isToolActive ? "working" : "committed",
4292
+ placementPolicy: this.getPlacementPolicy(),
4293
+ sessionNotice: this.sessionNotice
4208
4294
  };
4209
4295
  }
4210
4296
  emitImageStateChange() {
@@ -4253,6 +4339,7 @@ var ImageTool = class {
4253
4339
  }
4254
4340
  async addImageEntry(url, options, operation) {
4255
4341
  this.syncToolActiveFromWorkbench();
4342
+ this.clearSessionNotice({ emit: false });
4256
4343
  const id = this.generateId();
4257
4344
  const newItem = this.normalizeItem({
4258
4345
  id,
@@ -4313,7 +4400,11 @@ var ImageTool = class {
4313
4400
  }
4314
4401
  return { id: targetId, mode: "replace" };
4315
4402
  }
4316
- const id = await this.addImageEntry(url, options.addOptions, options.operation);
4403
+ const id = await this.addImageEntry(
4404
+ url,
4405
+ options.addOptions,
4406
+ options.operation
4407
+ );
4317
4408
  return { id, mode: "add" };
4318
4409
  }
4319
4410
  async updateImage(id, updates, options = {}) {
@@ -4351,6 +4442,7 @@ var ImageTool = class {
4351
4442
  }
4352
4443
  updateConfig(newItems, skipCanvasUpdate = false) {
4353
4444
  if (!this.context) return;
4445
+ this.clearSessionNotice({ emit: false });
4354
4446
  this.applyCommittedItems(newItems);
4355
4447
  runDeferredConfigUpdate(
4356
4448
  this,
@@ -4453,6 +4545,71 @@ var ImageTool = class {
4453
4545
  getCoverScale(frame, size) {
4454
4546
  return getCoverScale(frame, size);
4455
4547
  }
4548
+ resolvePlacementState(item) {
4549
+ var _a;
4550
+ return {
4551
+ left: Number.isFinite(item.left) ? item.left : 0.5,
4552
+ top: Number.isFinite(item.top) ? item.top : 0.5,
4553
+ scale: Math.max(0.05, (_a = item.scale) != null ? _a : 1),
4554
+ angle: Number.isFinite(item.angle) ? item.angle : 0
4555
+ };
4556
+ }
4557
+ async validatePlacementForItem(item) {
4558
+ const frame = this.getFrameRect();
4559
+ if (!frame.width || !frame.height) {
4560
+ return true;
4561
+ }
4562
+ const src = item.sourceUrl || item.url;
4563
+ if (!src) {
4564
+ return true;
4565
+ }
4566
+ const source = await this.resolveImageSourceSize(item.id, src);
4567
+ if (!source) {
4568
+ return true;
4569
+ }
4570
+ return validateImagePlacement({
4571
+ frame,
4572
+ source,
4573
+ placement: this.resolvePlacementState(item)
4574
+ }).ok;
4575
+ }
4576
+ async validateImageSession() {
4577
+ const policy = this.getPlacementPolicy();
4578
+ if (policy === "free") {
4579
+ this.clearSessionNotice();
4580
+ return { ok: true, policy };
4581
+ }
4582
+ const invalidImageIds = [];
4583
+ for (const item of this.workingItems) {
4584
+ const valid = await this.validatePlacementForItem(item);
4585
+ if (!valid) {
4586
+ invalidImageIds.push(item.id);
4587
+ }
4588
+ }
4589
+ if (!invalidImageIds.length) {
4590
+ this.clearSessionNotice();
4591
+ return { ok: true, policy };
4592
+ }
4593
+ const notice = {
4594
+ code: "image-outside-frame",
4595
+ level: policy === "strict" ? "error" : "warning",
4596
+ message: policy === "strict" ? "\u56FE\u7247\u4F4D\u7F6E\u4E0D\u80FD\u8D85\u51FA frame\uFF0C\u8BF7\u8C03\u6574\u540E\u518D\u63D0\u4EA4\u3002" : "\u56FE\u7247\u4F4D\u7F6E\u5DF2\u8D85\u51FA frame\uFF0C\u5EFA\u8BAE\u8C03\u6574\u540E\u518D\u63D0\u4EA4\u3002",
4597
+ imageIds: invalidImageIds,
4598
+ policy
4599
+ };
4600
+ this.setSessionNotice(notice);
4601
+ this.setImageFocus(invalidImageIds[0], {
4602
+ syncCanvasSelection: true,
4603
+ skipRender: true
4604
+ });
4605
+ return {
4606
+ ok: policy !== "strict",
4607
+ reason: notice.code,
4608
+ message: notice.message,
4609
+ imageIds: notice.imageIds,
4610
+ policy: notice.policy
4611
+ };
4612
+ }
4456
4613
  getFrameVisualConfig() {
4457
4614
  var _a, _b;
4458
4615
  const strokeStyleRaw = this.getConfig(
@@ -4737,6 +4894,7 @@ var ImageTool = class {
4737
4894
  await this.updateImage(id, next, options);
4738
4895
  }
4739
4896
  resetImageSession() {
4897
+ this.clearSessionNotice({ emit: false });
4740
4898
  this.workingItems = this.cloneItems(this.items);
4741
4899
  this.hasWorkingChanges = false;
4742
4900
  this.updateImages();
@@ -4745,6 +4903,7 @@ var ImageTool = class {
4745
4903
  updateImageInWorking(id, updates) {
4746
4904
  const index = this.workingItems.findIndex((item) => item.id === id);
4747
4905
  if (index < 0) return;
4906
+ this.clearSessionNotice({ emit: false });
4748
4907
  const next = [...this.workingItems];
4749
4908
  next[index] = this.normalizeItem({ ...next[index], ...updates });
4750
4909
  this.workingItems = next;
@@ -4761,6 +4920,7 @@ var ImageTool = class {
4761
4920
  async updateImageInConfig(id, updates) {
4762
4921
  const index = this.items.findIndex((item) => item.id === id);
4763
4922
  if (index < 0) return;
4923
+ this.clearSessionNotice({ emit: false });
4764
4924
  const replacingSource = typeof updates.url === "string" && updates.url.length > 0;
4765
4925
  const next = [...this.items];
4766
4926
  const base = next[index];
@@ -4869,11 +5029,42 @@ var ImageTool = class {
4869
5029
  }
4870
5030
  }
4871
5031
  this.hasWorkingChanges = false;
5032
+ this.clearSessionNotice({ emit: false });
4872
5033
  this.workingItems = this.cloneItems(next);
4873
5034
  this.updateConfig(next);
4874
5035
  this.emitWorkingChange(this.focusedImageId);
4875
5036
  return { ok: true };
4876
5037
  }
5038
+ async completeImageSession() {
5039
+ var _a, _b, _c;
5040
+ const sessionState = (_a = this.context) == null ? void 0 : _a.services.get("ToolSessionService");
5041
+ const workbench = (_b = this.context) == null ? void 0 : _b.services.get("WorkbenchService");
5042
+ console.info("[ImageTool] completeImageSession:start", {
5043
+ activeToolId: (_c = workbench == null ? void 0 : workbench.activeToolId) != null ? _c : null,
5044
+ isToolActive: this.isToolActive,
5045
+ dirtyBeforeComplete: this.hasWorkingChanges,
5046
+ workingCount: this.workingItems.length,
5047
+ committedCount: this.items.length,
5048
+ sessionDirty: sessionState == null ? void 0 : sessionState.isDirty(this.id)
5049
+ });
5050
+ const validation = await this.validateImageSession();
5051
+ if (!validation.ok) {
5052
+ console.warn("[ImageTool] completeImageSession:validation-failed", {
5053
+ validation,
5054
+ dirtyAfterValidation: this.hasWorkingChanges
5055
+ });
5056
+ return validation;
5057
+ }
5058
+ const result = await this.commitWorkingImagesAsCropped();
5059
+ console.info("[ImageTool] completeImageSession:done", {
5060
+ result,
5061
+ dirtyAfterComplete: this.hasWorkingChanges,
5062
+ workingCount: this.workingItems.length,
5063
+ committedCount: this.items.length,
5064
+ sessionDirty: sessionState == null ? void 0 : sessionState.isDirty(this.id)
5065
+ });
5066
+ return result;
5067
+ }
4877
5068
  async exportCroppedImageByIds(imageIds, options) {
4878
5069
  var _a, _b, _c;
4879
5070
  if (!this.canvasService) {
package/dist/index.mjs CHANGED
@@ -1302,12 +1302,20 @@ function createImageCommands(tool) {
1302
1302
  tool.resetImageSession();
1303
1303
  }
1304
1304
  },
1305
+ {
1306
+ command: "validateImageSession",
1307
+ id: "validateImageSession",
1308
+ title: "Validate Image Session",
1309
+ handler: async () => {
1310
+ return await tool.validateImageSession();
1311
+ }
1312
+ },
1305
1313
  {
1306
1314
  command: "completeImages",
1307
1315
  id: "completeImages",
1308
1316
  title: "Complete Images",
1309
1317
  handler: async () => {
1310
- return await tool.commitWorkingImagesAsCropped();
1318
+ return await tool.completeImageSession();
1311
1319
  }
1312
1320
  },
1313
1321
  {
@@ -1552,6 +1560,13 @@ function createImageConfigurations() {
1552
1560
  type: "color",
1553
1561
  label: "Image Frame Outer Background",
1554
1562
  default: "#f5f5f5"
1563
+ },
1564
+ {
1565
+ id: "image.session.placementPolicy",
1566
+ type: "select",
1567
+ label: "Image Session Placement Policy",
1568
+ options: ["free", "warn", "strict"],
1569
+ default: "free"
1555
1570
  }
1556
1571
  ];
1557
1572
  }
@@ -1628,6 +1643,44 @@ function computeImageOperationUpdates(args) {
1628
1643
  };
1629
1644
  }
1630
1645
 
1646
+ // src/extensions/image/imagePlacement.ts
1647
+ function toRadians(angle) {
1648
+ return angle * Math.PI / 180;
1649
+ }
1650
+ function validateImagePlacement(args) {
1651
+ const { frame, source, placement } = args;
1652
+ if (frame.width <= 0 || frame.height <= 0 || source.width <= 0 || source.height <= 0) {
1653
+ return { ok: true };
1654
+ }
1655
+ const coverScale = getCoverScale(frame, source);
1656
+ const imageWidth = source.width * coverScale * Math.max(0.05, Number(placement.scale || 1));
1657
+ const imageHeight = source.height * coverScale * Math.max(0.05, Number(placement.scale || 1));
1658
+ if (imageWidth <= 0 || imageHeight <= 0) {
1659
+ return { ok: true };
1660
+ }
1661
+ const centerX = frame.left + placement.left * frame.width;
1662
+ const centerY = frame.top + placement.top * frame.height;
1663
+ const halfWidth = imageWidth / 2;
1664
+ const halfHeight = imageHeight / 2;
1665
+ const radians = toRadians(placement.angle || 0);
1666
+ const cos = Math.cos(radians);
1667
+ const sin = Math.sin(radians);
1668
+ const frameCorners = [
1669
+ { x: frame.left, y: frame.top },
1670
+ { x: frame.left + frame.width, y: frame.top },
1671
+ { x: frame.left + frame.width, y: frame.top + frame.height },
1672
+ { x: frame.left, y: frame.top + frame.height }
1673
+ ];
1674
+ const coversFrame = frameCorners.every((corner) => {
1675
+ const dx = corner.x - centerX;
1676
+ const dy = corner.y - centerY;
1677
+ const localX = dx * cos + dy * sin;
1678
+ const localY = -dx * sin + dy * cos;
1679
+ return Math.abs(localX) <= halfWidth + 1e-6 && Math.abs(localY) <= halfHeight + 1e-6;
1680
+ });
1681
+ return { ok: coversFrame };
1682
+ }
1683
+
1631
1684
  // src/extensions/geometry.ts
1632
1685
  import paper from "paper";
1633
1686
 
@@ -2430,6 +2483,7 @@ var ImageTool = class {
2430
2483
  this.activeSnapX = null;
2431
2484
  this.activeSnapY = null;
2432
2485
  this.movingImageId = null;
2486
+ this.sessionNotice = null;
2433
2487
  this.hasRenderedSnapGuides = false;
2434
2488
  this.subscriptions = new SubscriptionBag();
2435
2489
  this.imageControlsByCapabilityKey = /* @__PURE__ */ new Map();
@@ -2629,7 +2683,10 @@ var ImageTool = class {
2629
2683
  this.updateImages();
2630
2684
  return;
2631
2685
  }
2632
- if (e.key.startsWith("size.") || e.key.startsWith("image.frame.") || e.key.startsWith("image.control.")) {
2686
+ if (e.key.startsWith("size.") || e.key.startsWith("image.frame.") || e.key.startsWith("image.session.") || e.key.startsWith("image.control.")) {
2687
+ if (e.key === "image.session.placementPolicy") {
2688
+ this.clearSessionNotice();
2689
+ }
2633
2690
  if (e.key.startsWith("image.control.")) {
2634
2691
  this.imageControlsByCapabilityKey.clear();
2635
2692
  }
@@ -3073,6 +3130,7 @@ var ImageTool = class {
3073
3130
  interaction: "session",
3074
3131
  commands: {
3075
3132
  begin: "imageSessionReset",
3133
+ validate: "validateImageSession",
3076
3134
  commit: "completeImages",
3077
3135
  rollback: "imageSessionReset"
3078
3136
  },
@@ -3111,6 +3169,32 @@ var ImageTool = class {
3111
3169
  getViewItems() {
3112
3170
  return this.isToolActive ? this.workingItems : this.items;
3113
3171
  }
3172
+ getPlacementPolicy() {
3173
+ const policy = this.getConfig(
3174
+ "image.session.placementPolicy",
3175
+ "free"
3176
+ );
3177
+ return policy === "warn" || policy === "strict" ? policy : "free";
3178
+ }
3179
+ areSessionNoticesEqual(a, b) {
3180
+ if (!a && !b) return true;
3181
+ if (!a || !b) return false;
3182
+ return a.code === b.code && a.level === b.level && a.message === b.message && a.policy === b.policy && JSON.stringify(a.imageIds) === JSON.stringify(b.imageIds);
3183
+ }
3184
+ setSessionNotice(notice, options = {}) {
3185
+ var _a;
3186
+ if (this.areSessionNoticesEqual(this.sessionNotice, notice)) {
3187
+ return;
3188
+ }
3189
+ this.sessionNotice = notice;
3190
+ if (options.emit !== false) {
3191
+ (_a = this.context) == null ? void 0 : _a.eventBus.emit("image:session:notice", this.sessionNotice);
3192
+ this.emitImageStateChange();
3193
+ }
3194
+ }
3195
+ clearSessionNotice(options = {}) {
3196
+ this.setSessionNotice(null, options);
3197
+ }
3114
3198
  getImageViewState() {
3115
3199
  this.syncToolActiveFromWorkbench();
3116
3200
  const items = this.cloneItems(this.getViewItems());
@@ -3123,7 +3207,9 @@ var ImageTool = class {
3123
3207
  isToolActive: this.isToolActive,
3124
3208
  isImageSelectionActive: this.isImageSelectionActive,
3125
3209
  hasWorkingChanges: this.hasWorkingChanges,
3126
- source: this.isToolActive ? "working" : "committed"
3210
+ source: this.isToolActive ? "working" : "committed",
3211
+ placementPolicy: this.getPlacementPolicy(),
3212
+ sessionNotice: this.sessionNotice
3127
3213
  };
3128
3214
  }
3129
3215
  emitImageStateChange() {
@@ -3172,6 +3258,7 @@ var ImageTool = class {
3172
3258
  }
3173
3259
  async addImageEntry(url, options, operation) {
3174
3260
  this.syncToolActiveFromWorkbench();
3261
+ this.clearSessionNotice({ emit: false });
3175
3262
  const id = this.generateId();
3176
3263
  const newItem = this.normalizeItem({
3177
3264
  id,
@@ -3232,7 +3319,11 @@ var ImageTool = class {
3232
3319
  }
3233
3320
  return { id: targetId, mode: "replace" };
3234
3321
  }
3235
- const id = await this.addImageEntry(url, options.addOptions, options.operation);
3322
+ const id = await this.addImageEntry(
3323
+ url,
3324
+ options.addOptions,
3325
+ options.operation
3326
+ );
3236
3327
  return { id, mode: "add" };
3237
3328
  }
3238
3329
  async updateImage(id, updates, options = {}) {
@@ -3270,6 +3361,7 @@ var ImageTool = class {
3270
3361
  }
3271
3362
  updateConfig(newItems, skipCanvasUpdate = false) {
3272
3363
  if (!this.context) return;
3364
+ this.clearSessionNotice({ emit: false });
3273
3365
  this.applyCommittedItems(newItems);
3274
3366
  runDeferredConfigUpdate(
3275
3367
  this,
@@ -3372,6 +3464,71 @@ var ImageTool = class {
3372
3464
  getCoverScale(frame, size) {
3373
3465
  return getCoverScale(frame, size);
3374
3466
  }
3467
+ resolvePlacementState(item) {
3468
+ var _a;
3469
+ return {
3470
+ left: Number.isFinite(item.left) ? item.left : 0.5,
3471
+ top: Number.isFinite(item.top) ? item.top : 0.5,
3472
+ scale: Math.max(0.05, (_a = item.scale) != null ? _a : 1),
3473
+ angle: Number.isFinite(item.angle) ? item.angle : 0
3474
+ };
3475
+ }
3476
+ async validatePlacementForItem(item) {
3477
+ const frame = this.getFrameRect();
3478
+ if (!frame.width || !frame.height) {
3479
+ return true;
3480
+ }
3481
+ const src = item.sourceUrl || item.url;
3482
+ if (!src) {
3483
+ return true;
3484
+ }
3485
+ const source = await this.resolveImageSourceSize(item.id, src);
3486
+ if (!source) {
3487
+ return true;
3488
+ }
3489
+ return validateImagePlacement({
3490
+ frame,
3491
+ source,
3492
+ placement: this.resolvePlacementState(item)
3493
+ }).ok;
3494
+ }
3495
+ async validateImageSession() {
3496
+ const policy = this.getPlacementPolicy();
3497
+ if (policy === "free") {
3498
+ this.clearSessionNotice();
3499
+ return { ok: true, policy };
3500
+ }
3501
+ const invalidImageIds = [];
3502
+ for (const item of this.workingItems) {
3503
+ const valid = await this.validatePlacementForItem(item);
3504
+ if (!valid) {
3505
+ invalidImageIds.push(item.id);
3506
+ }
3507
+ }
3508
+ if (!invalidImageIds.length) {
3509
+ this.clearSessionNotice();
3510
+ return { ok: true, policy };
3511
+ }
3512
+ const notice = {
3513
+ code: "image-outside-frame",
3514
+ level: policy === "strict" ? "error" : "warning",
3515
+ message: policy === "strict" ? "\u56FE\u7247\u4F4D\u7F6E\u4E0D\u80FD\u8D85\u51FA frame\uFF0C\u8BF7\u8C03\u6574\u540E\u518D\u63D0\u4EA4\u3002" : "\u56FE\u7247\u4F4D\u7F6E\u5DF2\u8D85\u51FA frame\uFF0C\u5EFA\u8BAE\u8C03\u6574\u540E\u518D\u63D0\u4EA4\u3002",
3516
+ imageIds: invalidImageIds,
3517
+ policy
3518
+ };
3519
+ this.setSessionNotice(notice);
3520
+ this.setImageFocus(invalidImageIds[0], {
3521
+ syncCanvasSelection: true,
3522
+ skipRender: true
3523
+ });
3524
+ return {
3525
+ ok: policy !== "strict",
3526
+ reason: notice.code,
3527
+ message: notice.message,
3528
+ imageIds: notice.imageIds,
3529
+ policy: notice.policy
3530
+ };
3531
+ }
3375
3532
  getFrameVisualConfig() {
3376
3533
  var _a, _b;
3377
3534
  const strokeStyleRaw = this.getConfig(
@@ -3656,6 +3813,7 @@ var ImageTool = class {
3656
3813
  await this.updateImage(id, next, options);
3657
3814
  }
3658
3815
  resetImageSession() {
3816
+ this.clearSessionNotice({ emit: false });
3659
3817
  this.workingItems = this.cloneItems(this.items);
3660
3818
  this.hasWorkingChanges = false;
3661
3819
  this.updateImages();
@@ -3664,6 +3822,7 @@ var ImageTool = class {
3664
3822
  updateImageInWorking(id, updates) {
3665
3823
  const index = this.workingItems.findIndex((item) => item.id === id);
3666
3824
  if (index < 0) return;
3825
+ this.clearSessionNotice({ emit: false });
3667
3826
  const next = [...this.workingItems];
3668
3827
  next[index] = this.normalizeItem({ ...next[index], ...updates });
3669
3828
  this.workingItems = next;
@@ -3680,6 +3839,7 @@ var ImageTool = class {
3680
3839
  async updateImageInConfig(id, updates) {
3681
3840
  const index = this.items.findIndex((item) => item.id === id);
3682
3841
  if (index < 0) return;
3842
+ this.clearSessionNotice({ emit: false });
3683
3843
  const replacingSource = typeof updates.url === "string" && updates.url.length > 0;
3684
3844
  const next = [...this.items];
3685
3845
  const base = next[index];
@@ -3788,11 +3948,42 @@ var ImageTool = class {
3788
3948
  }
3789
3949
  }
3790
3950
  this.hasWorkingChanges = false;
3951
+ this.clearSessionNotice({ emit: false });
3791
3952
  this.workingItems = this.cloneItems(next);
3792
3953
  this.updateConfig(next);
3793
3954
  this.emitWorkingChange(this.focusedImageId);
3794
3955
  return { ok: true };
3795
3956
  }
3957
+ async completeImageSession() {
3958
+ var _a, _b, _c;
3959
+ const sessionState = (_a = this.context) == null ? void 0 : _a.services.get("ToolSessionService");
3960
+ const workbench = (_b = this.context) == null ? void 0 : _b.services.get("WorkbenchService");
3961
+ console.info("[ImageTool] completeImageSession:start", {
3962
+ activeToolId: (_c = workbench == null ? void 0 : workbench.activeToolId) != null ? _c : null,
3963
+ isToolActive: this.isToolActive,
3964
+ dirtyBeforeComplete: this.hasWorkingChanges,
3965
+ workingCount: this.workingItems.length,
3966
+ committedCount: this.items.length,
3967
+ sessionDirty: sessionState == null ? void 0 : sessionState.isDirty(this.id)
3968
+ });
3969
+ const validation = await this.validateImageSession();
3970
+ if (!validation.ok) {
3971
+ console.warn("[ImageTool] completeImageSession:validation-failed", {
3972
+ validation,
3973
+ dirtyAfterValidation: this.hasWorkingChanges
3974
+ });
3975
+ return validation;
3976
+ }
3977
+ const result = await this.commitWorkingImagesAsCropped();
3978
+ console.info("[ImageTool] completeImageSession:done", {
3979
+ result,
3980
+ dirtyAfterComplete: this.hasWorkingChanges,
3981
+ workingCount: this.workingItems.length,
3982
+ committedCount: this.items.length,
3983
+ sessionDirty: sessionState == null ? void 0 : sessionState.isDirty(this.id)
3984
+ });
3985
+ return result;
3986
+ }
3796
3987
  async exportCroppedImageByIds(imageIds, options) {
3797
3988
  var _a, _b, _c;
3798
3989
  if (!this.canvasService) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pooder/kit",
3
- "version": "6.3.0",
3
+ "version": "6.3.1",
4
4
  "description": "Standard plugins for Pooder editor",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -19,7 +19,7 @@
19
19
  "dependencies": {
20
20
  "paper": "^0.12.18",
21
21
  "fabric": "^7.0.0",
22
- "@pooder/core": "2.2.1"
22
+ "@pooder/core": "2.2.2"
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsup src/index.ts --format cjs,esm --dts",