@pooder/kit 5.0.2 → 5.0.4

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
@@ -3005,6 +3005,8 @@ var FeatureTool = class {
3005
3005
  this.workingFeatures = [];
3006
3006
  this.isUpdatingConfig = false;
3007
3007
  this.isToolActive = false;
3008
+ this.isFeatureSessionActive = false;
3009
+ this.sessionOriginalFeatures = null;
3008
3010
  this.hasWorkingChanges = false;
3009
3011
  this.handleMoving = null;
3010
3012
  this.handleModified = null;
@@ -3012,6 +3014,9 @@ var FeatureTool = class {
3012
3014
  this.currentGeometry = null;
3013
3015
  this.onToolActivated = (event) => {
3014
3016
  this.isToolActive = event.id === this.id;
3017
+ if (!this.isToolActive) {
3018
+ this.restoreSessionFeaturesToConfig();
3019
+ }
3015
3020
  this.updateVisibility();
3016
3021
  };
3017
3022
  if (options) {
@@ -3035,6 +3040,7 @@ var FeatureTool = class {
3035
3040
  configService.onAnyChange((e) => {
3036
3041
  if (this.isUpdatingConfig) return;
3037
3042
  if (e.key === "dieline.features") {
3043
+ if (this.isFeatureSessionActive) return;
3038
3044
  const next = e.value || [];
3039
3045
  this.workingFeatures = this.cloneFeatures(next);
3040
3046
  this.hasWorkingChanges = false;
@@ -3054,6 +3060,7 @@ var FeatureTool = class {
3054
3060
  deactivate(context) {
3055
3061
  var _a;
3056
3062
  context.eventBus.off("tool:activated", this.onToolActivated);
3063
+ this.restoreSessionFeaturesToConfig();
3057
3064
  (_a = this.dirtyTrackerDisposable) == null ? void 0 : _a.dispose();
3058
3065
  this.dirtyTrackerDisposable = void 0;
3059
3066
  this.teardown();
@@ -3085,9 +3092,9 @@ var FeatureTool = class {
3085
3092
  name: "Feature",
3086
3093
  interaction: "session",
3087
3094
  commands: {
3088
- begin: "resetWorkingFeatures",
3095
+ begin: "beginFeatureSession",
3089
3096
  commit: "completeFeatures",
3090
- rollback: "resetWorkingFeatures"
3097
+ rollback: "rollbackFeatureSession"
3091
3098
  },
3092
3099
  session: {
3093
3100
  autoBegin: false,
@@ -3096,6 +3103,25 @@ var FeatureTool = class {
3096
3103
  }
3097
3104
  ],
3098
3105
  [import_core4.ContributionPointIds.COMMANDS]: [
3106
+ {
3107
+ command: "beginFeatureSession",
3108
+ title: "Begin Feature Session",
3109
+ handler: async () => {
3110
+ if (this.isFeatureSessionActive) {
3111
+ return { ok: true };
3112
+ }
3113
+ const original = this.getCommittedFeatures();
3114
+ this.sessionOriginalFeatures = this.cloneFeatures(original);
3115
+ this.isFeatureSessionActive = true;
3116
+ await this.refreshGeometry();
3117
+ this.setWorkingFeatures(this.cloneFeatures(original));
3118
+ this.hasWorkingChanges = false;
3119
+ this.redraw();
3120
+ this.emitWorkingChange();
3121
+ this.updateCommittedFeatures([]);
3122
+ return { ok: true };
3123
+ }
3124
+ },
3099
3125
  {
3100
3126
  command: "addFeature",
3101
3127
  title: "Add Edge Feature",
@@ -3148,19 +3174,27 @@ var FeatureTool = class {
3148
3174
  }
3149
3175
  },
3150
3176
  {
3151
- command: "resetWorkingFeatures",
3152
- title: "Reset Working Features",
3177
+ command: "rollbackFeatureSession",
3178
+ title: "Rollback Feature Session",
3153
3179
  handler: async () => {
3154
- var _a;
3155
- const configService = (_a = this.context) == null ? void 0 : _a.services.get(
3156
- "ConfigurationService"
3180
+ const original = this.cloneFeatures(
3181
+ this.sessionOriginalFeatures || this.getCommittedFeatures()
3157
3182
  );
3158
- const next = (configService == null ? void 0 : configService.get("dieline.features", [])) || [];
3159
3183
  await this.refreshGeometry();
3160
- this.setWorkingFeatures(this.cloneFeatures(next));
3184
+ this.setWorkingFeatures(original);
3161
3185
  this.hasWorkingChanges = false;
3162
3186
  this.redraw();
3163
3187
  this.emitWorkingChange();
3188
+ this.updateCommittedFeatures(original);
3189
+ this.clearFeatureSessionState();
3190
+ return { ok: true };
3191
+ }
3192
+ },
3193
+ {
3194
+ command: "resetWorkingFeatures",
3195
+ title: "Reset Working Features",
3196
+ handler: async () => {
3197
+ await this.resetWorkingFeaturesFromSource();
3164
3198
  return { ok: true };
3165
3199
  }
3166
3200
  },
@@ -3184,6 +3218,37 @@ var FeatureTool = class {
3184
3218
  cloneFeatures(features) {
3185
3219
  return JSON.parse(JSON.stringify(features || []));
3186
3220
  }
3221
+ getConfigService() {
3222
+ var _a;
3223
+ return (_a = this.context) == null ? void 0 : _a.services.get("ConfigurationService");
3224
+ }
3225
+ getCommittedFeatures() {
3226
+ const configService = this.getConfigService();
3227
+ const committed = (configService == null ? void 0 : configService.get("dieline.features", [])) || [];
3228
+ return this.cloneFeatures(committed);
3229
+ }
3230
+ updateCommittedFeatures(next) {
3231
+ const configService = this.getConfigService();
3232
+ if (!configService) return;
3233
+ this.isUpdatingConfig = true;
3234
+ try {
3235
+ configService.update("dieline.features", next);
3236
+ } finally {
3237
+ this.isUpdatingConfig = false;
3238
+ }
3239
+ }
3240
+ clearFeatureSessionState() {
3241
+ this.isFeatureSessionActive = false;
3242
+ this.sessionOriginalFeatures = null;
3243
+ }
3244
+ restoreSessionFeaturesToConfig() {
3245
+ if (!this.isFeatureSessionActive) return;
3246
+ const original = this.cloneFeatures(
3247
+ this.sessionOriginalFeatures || this.getCommittedFeatures()
3248
+ );
3249
+ this.updateCommittedFeatures(original);
3250
+ this.clearFeatureSessionState();
3251
+ }
3187
3252
  emitWorkingChange() {
3188
3253
  var _a;
3189
3254
  (_a = this.context) == null ? void 0 : _a.eventBus.emit("feature:working:change", {
@@ -3202,6 +3267,16 @@ var FeatureTool = class {
3202
3267
  } catch (e) {
3203
3268
  }
3204
3269
  }
3270
+ async resetWorkingFeaturesFromSource() {
3271
+ const next = this.cloneFeatures(
3272
+ this.isFeatureSessionActive && this.sessionOriginalFeatures ? this.sessionOriginalFeatures : this.getCommittedFeatures()
3273
+ );
3274
+ await this.refreshGeometry();
3275
+ this.setWorkingFeatures(next);
3276
+ this.hasWorkingChanges = false;
3277
+ this.redraw();
3278
+ this.emitWorkingChange();
3279
+ }
3205
3280
  setWorkingFeatures(next) {
3206
3281
  this.workingFeatures = next;
3207
3282
  }
@@ -3258,12 +3333,7 @@ var FeatureTool = class {
3258
3333
  this.workingFeatures,
3259
3334
  { dielineWidth, dielineHeight },
3260
3335
  (next) => {
3261
- this.isUpdatingConfig = true;
3262
- try {
3263
- configService.update("dieline.features", next);
3264
- } finally {
3265
- this.isUpdatingConfig = false;
3266
- }
3336
+ this.updateCommittedFeatures(next);
3267
3337
  this.workingFeatures = this.cloneFeatures(next);
3268
3338
  this.emitWorkingChange();
3269
3339
  }
@@ -3275,6 +3345,7 @@ var FeatureTool = class {
3275
3345
  };
3276
3346
  }
3277
3347
  this.hasWorkingChanges = false;
3348
+ this.clearFeatureSessionState();
3278
3349
  this.redraw();
3279
3350
  return { ok: true };
3280
3351
  }
@@ -3719,7 +3790,6 @@ var import_core5 = require("@pooder/core");
3719
3790
  var import_fabric5 = require("fabric");
3720
3791
  var IMAGE_OBJECT_LAYER_ID2 = "image.user";
3721
3792
  var IMAGE_OVERLAY_LAYER_ID = "image-overlay";
3722
- var IMAGE_REPLACE_GUARD_MS = 2500;
3723
3793
  var ImageTool = class {
3724
3794
  constructor() {
3725
3795
  this.id = "pooder.kit.image";
@@ -3735,18 +3805,15 @@ var ImageTool = class {
3735
3805
  this.isToolActive = false;
3736
3806
  this.isImageSelectionActive = false;
3737
3807
  this.focusedImageId = null;
3738
- this.suppressSelectionClearUntil = 0;
3739
3808
  this.renderSeq = 0;
3740
3809
  this.onToolActivated = (event) => {
3741
3810
  const before = this.isToolActive;
3742
3811
  this.syncToolActiveFromWorkbench(event.id);
3743
3812
  if (!this.isToolActive) {
3744
- const now = Date.now();
3745
- const inGuardWindow = now <= this.suppressSelectionClearUntil && !!this.focusedImageId;
3746
- if (!inGuardWindow) {
3747
- this.isImageSelectionActive = false;
3748
- this.focusedImageId = null;
3749
- }
3813
+ this.setImageFocus(null, {
3814
+ syncCanvasSelection: true,
3815
+ skipRender: true
3816
+ });
3750
3817
  }
3751
3818
  this.debug("tool:activated", {
3752
3819
  id: event.id,
@@ -3754,8 +3821,7 @@ var ImageTool = class {
3754
3821
  reason: event.reason,
3755
3822
  before,
3756
3823
  isToolActive: this.isToolActive,
3757
- focusedImageId: this.focusedImageId,
3758
- suppressSelectionClearUntil: this.suppressSelectionClearUntil
3824
+ focusedImageId: this.focusedImageId
3759
3825
  });
3760
3826
  if (!this.isToolActive && this.isDebugEnabled()) {
3761
3827
  console.trace("[ImageTool] tool deactivated trace");
@@ -3794,16 +3860,10 @@ var ImageTool = class {
3794
3860
  this.updateImages();
3795
3861
  };
3796
3862
  this.onSelectionCleared = () => {
3797
- const now = Date.now();
3798
- if (now <= this.suppressSelectionClearUntil && this.focusedImageId) {
3799
- this.debug("selection:cleared ignored", {
3800
- suppressUntil: this.suppressSelectionClearUntil,
3801
- focusedImageId: this.focusedImageId
3802
- });
3803
- return;
3804
- }
3805
- this.isImageSelectionActive = false;
3806
- this.focusedImageId = null;
3863
+ this.setImageFocus(null, {
3864
+ syncCanvasSelection: false,
3865
+ skipRender: true
3866
+ });
3807
3867
  this.debug("selection:cleared applied");
3808
3868
  this.updateImages();
3809
3869
  };
@@ -3959,7 +4019,7 @@ var ImageTool = class {
3959
4019
  id: "image.frame.strokeColor",
3960
4020
  type: "color",
3961
4021
  label: "Image Frame Stroke Color",
3962
- default: "#FF0000"
4022
+ default: "#808080"
3963
4023
  },
3964
4024
  {
3965
4025
  id: "image.frame.strokeWidth",
@@ -3975,7 +4035,7 @@ var ImageTool = class {
3975
4035
  type: "select",
3976
4036
  label: "Image Frame Stroke Style",
3977
4037
  options: ["solid", "dashed", "hidden"],
3978
- default: "solid"
4038
+ default: "dashed"
3979
4039
  },
3980
4040
  {
3981
4041
  id: "image.frame.dashLength",
@@ -4070,6 +4130,13 @@ var ImageTool = class {
4070
4130
  await this.fitImageToDefaultArea(id);
4071
4131
  }
4072
4132
  },
4133
+ {
4134
+ command: "focusImage",
4135
+ title: "Focus Image",
4136
+ handler: (id, options = {}) => {
4137
+ return this.setImageFocus(id, options);
4138
+ }
4139
+ },
4073
4140
  {
4074
4141
  command: "removeImage",
4075
4142
  title: "Remove Image",
@@ -4079,8 +4146,10 @@ var ImageTool = class {
4079
4146
  if (next.length !== this.items.length) {
4080
4147
  this.purgeSourceSizeCacheForItem(removed);
4081
4148
  if (this.focusedImageId === id) {
4082
- this.focusedImageId = null;
4083
- this.isImageSelectionActive = false;
4149
+ this.setImageFocus(null, {
4150
+ syncCanvasSelection: true,
4151
+ skipRender: true
4152
+ });
4084
4153
  }
4085
4154
  this.updateConfig(next);
4086
4155
  }
@@ -4098,8 +4167,10 @@ var ImageTool = class {
4098
4167
  title: "Clear Images",
4099
4168
  handler: () => {
4100
4169
  this.sourceSizeBySrc.clear();
4101
- this.focusedImageId = null;
4102
- this.isImageSelectionActive = false;
4170
+ this.setImageFocus(null, {
4171
+ syncCanvasSelection: true,
4172
+ skipRender: true
4173
+ });
4103
4174
  this.updateConfig([]);
4104
4175
  }
4105
4176
  },
@@ -4164,22 +4235,38 @@ var ImageTool = class {
4164
4235
  generateId() {
4165
4236
  return Math.random().toString(36).substring(2, 9);
4166
4237
  }
4167
- getImageIdFromActiveObject() {
4168
- var _a, _b, _c;
4169
- const active = (_a = this.canvasService) == null ? void 0 : _a.canvas.getActiveObject();
4170
- if (((_b = active == null ? void 0 : active.data) == null ? void 0 : _b.layerId) === IMAGE_OBJECT_LAYER_ID2 && typeof ((_c = active == null ? void 0 : active.data) == null ? void 0 : _c.id) === "string") {
4171
- return active.data.id;
4172
- }
4173
- return null;
4238
+ hasImageItem(id) {
4239
+ return this.items.some((item) => item.id === id) || this.workingItems.some((item) => item.id === id);
4174
4240
  }
4175
- resolveReplaceTargetId(explicitId) {
4176
- const has = (id) => !!id && this.items.some((item) => item.id === id);
4177
- if (has(explicitId)) return explicitId;
4178
- if (has(this.focusedImageId)) return this.focusedImageId;
4179
- const activeId = this.getImageIdFromActiveObject();
4180
- if (has(activeId)) return activeId;
4181
- if (this.items.length === 1) return this.items[0].id;
4182
- return null;
4241
+ setImageFocus(id, options = {}) {
4242
+ const syncCanvasSelection = options.syncCanvasSelection !== false;
4243
+ if (id && !this.hasImageItem(id)) {
4244
+ return { ok: false, reason: "image-not-found" };
4245
+ }
4246
+ this.focusedImageId = id;
4247
+ this.isImageSelectionActive = !!id;
4248
+ if (syncCanvasSelection && this.canvasService) {
4249
+ const canvas = this.canvasService.canvas;
4250
+ if (!id) {
4251
+ canvas.discardActiveObject();
4252
+ } else {
4253
+ const obj = this.getImageObject(id);
4254
+ if (obj) {
4255
+ obj.set({
4256
+ selectable: true,
4257
+ evented: true,
4258
+ hasControls: true,
4259
+ hasBorders: true
4260
+ });
4261
+ canvas.setActiveObject(obj);
4262
+ }
4263
+ }
4264
+ this.canvasService.requestRenderAll();
4265
+ }
4266
+ if (!options.skipRender) {
4267
+ this.updateImages();
4268
+ }
4269
+ return { ok: true, id };
4183
4270
  }
4184
4271
  async addImageEntry(url, options, fitOnAdd = true) {
4185
4272
  const id = this.generateId();
@@ -4189,9 +4276,6 @@ var ImageTool = class {
4189
4276
  opacity: 1,
4190
4277
  ...options
4191
4278
  });
4192
- this.focusedImageId = id;
4193
- this.isImageSelectionActive = true;
4194
- this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
4195
4279
  const sessionDirtyBeforeAdd = this.isToolActive && this.hasWorkingChanges;
4196
4280
  const waitLoaded = this.waitImageLoaded(id, true);
4197
4281
  this.updateConfig([...this.items, newItem]);
@@ -4201,26 +4285,24 @@ var ImageTool = class {
4201
4285
  await this.fitImageToDefaultArea(id);
4202
4286
  }
4203
4287
  if (loaded) {
4204
- this.focusImageSelection(id);
4288
+ this.setImageFocus(id);
4205
4289
  }
4206
4290
  return id;
4207
4291
  }
4208
4292
  async upsertImageEntry(url, options = {}) {
4209
- var _a;
4210
- const mode = options.mode || "auto";
4293
+ const mode = options.mode || (options.id ? "replace" : "add");
4211
4294
  const fitOnAdd = options.fitOnAdd !== false;
4212
- if (mode === "add") {
4213
- const id2 = await this.addImageEntry(url, options.addOptions, fitOnAdd);
4214
- return { id: id2, mode: "add" };
4215
- }
4216
- const targetId = this.resolveReplaceTargetId((_a = options.id) != null ? _a : null);
4217
- if (targetId) {
4295
+ if (mode === "replace") {
4296
+ if (!options.id) {
4297
+ throw new Error("replace-target-id-required");
4298
+ }
4299
+ const targetId = options.id;
4300
+ if (!this.hasImageItem(targetId)) {
4301
+ throw new Error("replace-target-not-found");
4302
+ }
4218
4303
  await this.updateImageInConfig(targetId, { url });
4219
4304
  return { id: targetId, mode: "replace" };
4220
4305
  }
4221
- if (mode === "replace" || options.createIfMissing === false) {
4222
- throw new Error("replace-target-not-found");
4223
- }
4224
4306
  const id = await this.addImageEntry(url, options.addOptions, fitOnAdd);
4225
4307
  return { id, mode: "add" };
4226
4308
  }
@@ -4395,9 +4477,9 @@ var ImageTool = class {
4395
4477
  var _a, _b;
4396
4478
  const strokeStyleRaw = this.getConfig(
4397
4479
  "image.frame.strokeStyle",
4398
- "solid"
4399
- ) || "solid";
4400
- const strokeStyle = strokeStyleRaw === "dashed" || strokeStyleRaw === "hidden" ? strokeStyleRaw : "solid";
4480
+ "dashed"
4481
+ ) || "dashed";
4482
+ const strokeStyle = strokeStyleRaw === "dashed" || strokeStyleRaw === "hidden" ? strokeStyleRaw : "dashed";
4401
4483
  const strokeWidth = Number(
4402
4484
  (_a = this.getConfig("image.frame.strokeWidth", 2)) != null ? _a : 2
4403
4485
  );
@@ -4405,7 +4487,7 @@ var ImageTool = class {
4405
4487
  (_b = this.getConfig("image.frame.dashLength", 8)) != null ? _b : 8
4406
4488
  );
4407
4489
  return {
4408
- strokeColor: this.getConfig("image.frame.strokeColor", "#FF0000") || "#FF0000",
4490
+ strokeColor: this.getConfig("image.frame.strokeColor", "#808080") || "#808080",
4409
4491
  strokeWidth: Number.isFinite(strokeWidth) ? Math.max(0, strokeWidth) : 2,
4410
4492
  strokeStyle,
4411
4493
  dashLength: Number.isFinite(dashLength) ? Math.max(1, dashLength) : 8,
@@ -4670,8 +4752,10 @@ var ImageTool = class {
4670
4752
  const frame = this.getFrameRect();
4671
4753
  const desiredIds = new Set(renderItems.map((item) => item.id));
4672
4754
  if (this.focusedImageId && !desiredIds.has(this.focusedImageId)) {
4673
- this.focusedImageId = null;
4674
- this.isImageSelectionActive = false;
4755
+ this.setImageFocus(null, {
4756
+ syncCanvasSelection: false,
4757
+ skipRender: true
4758
+ });
4675
4759
  }
4676
4760
  this.getImageObjects().forEach((obj) => {
4677
4761
  var _a, _b;
@@ -4714,8 +4798,10 @@ var ImageTool = class {
4714
4798
  next[index] = this.normalizeItem({ ...next[index], ...updates });
4715
4799
  this.workingItems = next;
4716
4800
  this.hasWorkingChanges = true;
4717
- this.isImageSelectionActive = true;
4718
- this.focusedImageId = id;
4801
+ this.setImageFocus(id, {
4802
+ syncCanvasSelection: false,
4803
+ skipRender: true
4804
+ });
4719
4805
  if (this.isToolActive) {
4720
4806
  this.updateImages();
4721
4807
  }
@@ -4744,16 +4830,13 @@ var ImageTool = class {
4744
4830
  });
4745
4831
  this.updateConfig(next);
4746
4832
  if (replacingSource) {
4747
- this.focusedImageId = id;
4748
- this.isImageSelectionActive = true;
4749
- this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
4750
4833
  this.debug("replace:image:begin", { id, replacingUrl });
4751
4834
  this.purgeSourceSizeCacheForItem(base);
4752
4835
  const loaded = await this.waitImageLoaded(id, true);
4753
4836
  this.debug("replace:image:loaded", { id, loaded });
4754
4837
  if (loaded) {
4755
4838
  await this.refitImageToFrame(id);
4756
- this.focusImageSelection(id);
4839
+ this.setImageFocus(id);
4757
4840
  }
4758
4841
  }
4759
4842
  }
@@ -4797,29 +4880,9 @@ var ImageTool = class {
4797
4880
  this.updateConfig(next);
4798
4881
  this.workingItems = this.cloneItems(next);
4799
4882
  this.hasWorkingChanges = false;
4800
- this.isImageSelectionActive = true;
4801
- this.focusedImageId = id;
4802
4883
  this.updateImages();
4803
4884
  this.emitWorkingChange(id);
4804
4885
  }
4805
- focusImageSelection(id) {
4806
- if (!this.canvasService) return;
4807
- const obj = this.getImageObject(id);
4808
- if (!obj) return;
4809
- this.isImageSelectionActive = true;
4810
- this.focusedImageId = id;
4811
- this.suppressSelectionClearUntil = Date.now() + 700;
4812
- obj.set({
4813
- selectable: true,
4814
- evented: true,
4815
- hasControls: true,
4816
- hasBorders: true
4817
- });
4818
- this.canvasService.canvas.setActiveObject(obj);
4819
- this.debug("focus:image", { id });
4820
- this.canvasService.requestRenderAll();
4821
- this.updateImages();
4822
- }
4823
4886
  async fitImageToArea(id, area) {
4824
4887
  var _a, _b;
4825
4888
  if (!this.canvasService) return;
@@ -4869,7 +4932,6 @@ var ImageTool = class {
4869
4932
  if (!frame.width || !frame.height) {
4870
4933
  return { ok: false, reason: "frame-not-ready" };
4871
4934
  }
4872
- const focusId = this.resolveReplaceTargetId(this.focusedImageId) || (this.workingItems.length === 1 ? this.workingItems[0].id : null);
4873
4935
  const next = [];
4874
4936
  for (const item of this.workingItems) {
4875
4937
  const url = await this.exportCroppedImageByIds([item.id], {
@@ -4893,13 +4955,7 @@ var ImageTool = class {
4893
4955
  this.hasWorkingChanges = false;
4894
4956
  this.workingItems = this.cloneItems(next);
4895
4957
  this.updateConfig(next);
4896
- this.emitWorkingChange(focusId);
4897
- if (focusId) {
4898
- this.focusedImageId = focusId;
4899
- this.isImageSelectionActive = true;
4900
- this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
4901
- this.focusImageSelection(focusId);
4902
- }
4958
+ this.emitWorkingChange(this.focusedImageId);
4903
4959
  return { ok: true };
4904
4960
  }
4905
4961
  async exportCroppedImageByIds(imageIds, options) {