@pooder/kit 5.0.1 → 5.0.3

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/src/feature.ts CHANGED
@@ -34,6 +34,8 @@ export class FeatureTool implements Extension {
34
34
  private context?: ExtensionContext;
35
35
  private isUpdatingConfig = false;
36
36
  private isToolActive = false;
37
+ private isFeatureSessionActive = false;
38
+ private sessionOriginalFeatures: ConstraintFeature[] | null = null;
37
39
  private hasWorkingChanges = false;
38
40
  private dirtyTrackerDisposable?: { dispose(): void };
39
41
 
@@ -76,6 +78,7 @@ export class FeatureTool implements Extension {
76
78
  if (this.isUpdatingConfig) return;
77
79
 
78
80
  if (e.key === "dieline.features") {
81
+ if (this.isFeatureSessionActive) return;
79
82
  const next = (e.value || []) as ConstraintFeature[];
80
83
  this.workingFeatures = this.cloneFeatures(next);
81
84
  this.hasWorkingChanges = false;
@@ -100,6 +103,7 @@ export class FeatureTool implements Extension {
100
103
 
101
104
  deactivate(context: ExtensionContext) {
102
105
  context.eventBus.off("tool:activated", this.onToolActivated);
106
+ this.restoreSessionFeaturesToConfig();
103
107
  this.dirtyTrackerDisposable?.dispose();
104
108
  this.dirtyTrackerDisposable = undefined;
105
109
  this.teardown();
@@ -107,8 +111,11 @@ export class FeatureTool implements Extension {
107
111
  this.context = undefined;
108
112
  }
109
113
 
110
- private onToolActivated = (event: { id: string }) => {
114
+ private onToolActivated = (event: { id: string | null }) => {
111
115
  this.isToolActive = event.id === this.id;
116
+ if (!this.isToolActive) {
117
+ this.restoreSessionFeaturesToConfig();
118
+ }
112
119
  this.updateVisibility();
113
120
  };
114
121
 
@@ -140,9 +147,9 @@ export class FeatureTool implements Extension {
140
147
  name: "Feature",
141
148
  interaction: "session",
142
149
  commands: {
143
- begin: "resetWorkingFeatures",
150
+ begin: "beginFeatureSession",
144
151
  commit: "completeFeatures",
145
- rollback: "resetWorkingFeatures",
152
+ rollback: "rollbackFeatureSession",
146
153
  },
147
154
  session: {
148
155
  autoBegin: false,
@@ -151,6 +158,25 @@ export class FeatureTool implements Extension {
151
158
  },
152
159
  ],
153
160
  [ContributionPointIds.COMMANDS]: [
161
+ {
162
+ command: "beginFeatureSession",
163
+ title: "Begin Feature Session",
164
+ handler: async () => {
165
+ if (this.isFeatureSessionActive) {
166
+ return { ok: true };
167
+ }
168
+ const original = this.getCommittedFeatures();
169
+ this.sessionOriginalFeatures = this.cloneFeatures(original);
170
+ this.isFeatureSessionActive = true;
171
+ await this.refreshGeometry();
172
+ this.setWorkingFeatures(this.cloneFeatures(original));
173
+ this.hasWorkingChanges = false;
174
+ this.redraw();
175
+ this.emitWorkingChange();
176
+ this.updateCommittedFeatures([]);
177
+ return { ok: true };
178
+ },
179
+ },
154
180
  {
155
181
  command: "addFeature",
156
182
  title: "Add Edge Feature",
@@ -203,21 +229,27 @@ export class FeatureTool implements Extension {
203
229
  },
204
230
  },
205
231
  {
206
- command: "resetWorkingFeatures",
207
- title: "Reset Working Features",
232
+ command: "rollbackFeatureSession",
233
+ title: "Rollback Feature Session",
208
234
  handler: async () => {
209
- const configService =
210
- this.context?.services.get<ConfigurationService>(
211
- "ConfigurationService",
212
- );
213
- const next = (configService?.get("dieline.features", []) ||
214
- []) as ConstraintFeature[];
215
-
235
+ const original = this.cloneFeatures(
236
+ this.sessionOriginalFeatures || this.getCommittedFeatures(),
237
+ );
216
238
  await this.refreshGeometry();
217
- this.setWorkingFeatures(this.cloneFeatures(next));
239
+ this.setWorkingFeatures(original);
218
240
  this.hasWorkingChanges = false;
219
241
  this.redraw();
220
242
  this.emitWorkingChange();
243
+ this.updateCommittedFeatures(original);
244
+ this.clearFeatureSessionState();
245
+ return { ok: true };
246
+ },
247
+ },
248
+ {
249
+ command: "resetWorkingFeatures",
250
+ title: "Reset Working Features",
251
+ handler: async () => {
252
+ await this.resetWorkingFeaturesFromSource();
221
253
  return { ok: true };
222
254
  },
223
255
  },
@@ -243,6 +275,42 @@ export class FeatureTool implements Extension {
243
275
  return JSON.parse(JSON.stringify(features || [])) as ConstraintFeature[];
244
276
  }
245
277
 
278
+ private getConfigService(): ConfigurationService | undefined {
279
+ return this.context?.services.get<ConfigurationService>("ConfigurationService");
280
+ }
281
+
282
+ private getCommittedFeatures(): ConstraintFeature[] {
283
+ const configService = this.getConfigService();
284
+ const committed = (configService?.get("dieline.features", []) ||
285
+ []) as ConstraintFeature[];
286
+ return this.cloneFeatures(committed);
287
+ }
288
+
289
+ private updateCommittedFeatures(next: ConstraintFeature[]) {
290
+ const configService = this.getConfigService();
291
+ if (!configService) return;
292
+ this.isUpdatingConfig = true;
293
+ try {
294
+ configService.update("dieline.features", next);
295
+ } finally {
296
+ this.isUpdatingConfig = false;
297
+ }
298
+ }
299
+
300
+ private clearFeatureSessionState() {
301
+ this.isFeatureSessionActive = false;
302
+ this.sessionOriginalFeatures = null;
303
+ }
304
+
305
+ private restoreSessionFeaturesToConfig() {
306
+ if (!this.isFeatureSessionActive) return;
307
+ const original = this.cloneFeatures(
308
+ this.sessionOriginalFeatures || this.getCommittedFeatures(),
309
+ );
310
+ this.updateCommittedFeatures(original);
311
+ this.clearFeatureSessionState();
312
+ }
313
+
246
314
  private emitWorkingChange() {
247
315
  this.context?.eventBus.emit("feature:working:change", {
248
316
  features: this.cloneFeatures(this.workingFeatures),
@@ -261,6 +329,19 @@ export class FeatureTool implements Extension {
261
329
  } catch (e) {}
262
330
  }
263
331
 
332
+ private async resetWorkingFeaturesFromSource() {
333
+ const next = this.cloneFeatures(
334
+ this.isFeatureSessionActive && this.sessionOriginalFeatures
335
+ ? this.sessionOriginalFeatures
336
+ : this.getCommittedFeatures(),
337
+ );
338
+ await this.refreshGeometry();
339
+ this.setWorkingFeatures(next);
340
+ this.hasWorkingChanges = false;
341
+ this.redraw();
342
+ this.emitWorkingChange();
343
+ }
344
+
264
345
  private setWorkingFeatures(next: ConstraintFeature[]) {
265
346
  this.workingFeatures = next;
266
347
  }
@@ -335,13 +416,7 @@ export class FeatureTool implements Extension {
335
416
  this.workingFeatures,
336
417
  { dielineWidth, dielineHeight },
337
418
  (next) => {
338
- this.isUpdatingConfig = true;
339
- try {
340
- configService.update("dieline.features", next);
341
- } finally {
342
- this.isUpdatingConfig = false;
343
- }
344
-
419
+ this.updateCommittedFeatures(next as ConstraintFeature[]);
345
420
  this.workingFeatures = this.cloneFeatures(next as any);
346
421
  this.emitWorkingChange();
347
422
  },
@@ -355,6 +430,7 @@ export class FeatureTool implements Extension {
355
430
  }
356
431
 
357
432
  this.hasWorkingChanges = false;
433
+ this.clearFeatureSessionState();
358
434
  // Keep feature markers above dieline overlay after config-driven redraw.
359
435
  this.redraw();
360
436
  return { ok: true };
package/src/image.ts CHANGED
@@ -148,10 +148,7 @@ export class ImageTool implements Extension {
148
148
  context.eventBus.on("selection:created", this.onSelectionChanged);
149
149
  context.eventBus.on("selection:updated", this.onSelectionChanged);
150
150
  context.eventBus.on("selection:cleared", this.onSelectionCleared);
151
- context.eventBus.on(
152
- "scene:layout:change",
153
- this.onSceneLayoutChanged,
154
- );
151
+ context.eventBus.on("scene:layout:change", this.onSceneLayoutChanged);
155
152
 
156
153
  const configService = context.services.get<ConfigurationService>(
157
154
  "ConfigurationService",
@@ -198,10 +195,7 @@ export class ImageTool implements Extension {
198
195
  context.eventBus.off("selection:created", this.onSelectionChanged);
199
196
  context.eventBus.off("selection:updated", this.onSelectionChanged);
200
197
  context.eventBus.off("selection:cleared", this.onSelectionCleared);
201
- context.eventBus.off(
202
- "scene:layout:change",
203
- this.onSceneLayoutChanged,
204
- );
198
+ context.eventBus.off("scene:layout:change", this.onSceneLayoutChanged);
205
199
  this.dirtyTrackerDisposable?.dispose();
206
200
  this.dirtyTrackerDisposable = undefined;
207
201
 
@@ -396,7 +390,7 @@ export class ImageTool implements Extension {
396
390
  id: "image.frame.outerBackground",
397
391
  type: "color",
398
392
  label: "Image Frame Outer Background",
399
- default: "rgba(0,0,0,0.18)",
393
+ default: "#f5f5f5",
400
394
  },
401
395
  ] as ConfigurationContribution[],
402
396
  [ContributionPointIds.COMMANDS]: [
@@ -439,6 +433,7 @@ export class ImageTool implements Extension {
439
433
  this.workingItems = this.cloneItems(this.items);
440
434
  this.hasWorkingChanges = false;
441
435
  this.updateImages();
436
+ this.emitWorkingChange();
442
437
  },
443
438
  },
444
439
  {
@@ -578,6 +573,13 @@ export class ImageTool implements Extension {
578
573
  return this.normalizeItems((items || []).map((i) => ({ ...i })));
579
574
  }
580
575
 
576
+ private emitWorkingChange(changedId: string | null = null) {
577
+ this.context?.eventBus.emit("image:working:change", {
578
+ changedId,
579
+ items: this.cloneItems(this.workingItems),
580
+ });
581
+ }
582
+
581
583
  private generateId(): string {
582
584
  return Math.random().toString(36).substring(2, 9);
583
585
  }
@@ -670,6 +672,7 @@ export class ImageTool implements Extension {
670
672
  if (this.workingItems.some((existing) => existing.id === item.id)) return;
671
673
  this.workingItems = this.cloneItems([...this.workingItems, item]);
672
674
  this.updateImages();
675
+ this.emitWorkingChange(item.id);
673
676
  }
674
677
 
675
678
  private async updateImage(
@@ -893,11 +896,7 @@ export class ImageTool implements Extension {
893
896
  "image.frame.innerBackground",
894
897
  "rgba(0,0,0,0)",
895
898
  ) || "rgba(0,0,0,0)",
896
- outerBackground:
897
- this.getConfig<string>(
898
- "image.frame.outerBackground",
899
- "rgba(0,0,0,0.18)",
900
- ) || "rgba(0,0,0,0.18)",
899
+ outerBackground: "#f5f5f5",
901
900
  };
902
901
  }
903
902
 
@@ -1074,10 +1073,22 @@ export class ImageTool implements Extension {
1074
1073
  const canvasH = this.canvasService.canvas.height || 0;
1075
1074
  const visual = this.getFrameVisualConfig();
1076
1075
 
1077
- const topH = Math.max(0, frame.top);
1078
- const bottomH = Math.max(0, canvasH - (frame.top + frame.height));
1079
- const leftW = Math.max(0, frame.left);
1080
- const rightW = Math.max(0, canvasW - (frame.left + frame.width));
1076
+ const frameLeft = Math.max(0, Math.min(canvasW, frame.left));
1077
+ const frameTop = Math.max(0, Math.min(canvasH, frame.top));
1078
+ const frameRight = Math.max(
1079
+ frameLeft,
1080
+ Math.min(canvasW, frame.left + frame.width),
1081
+ );
1082
+ const frameBottom = Math.max(
1083
+ frameTop,
1084
+ Math.min(canvasH, frame.top + frame.height),
1085
+ );
1086
+ const visibleFrameH = Math.max(0, frameBottom - frameTop);
1087
+
1088
+ const topH = frameTop;
1089
+ const bottomH = Math.max(0, canvasH - frameBottom);
1090
+ const leftW = frameLeft;
1091
+ const rightW = Math.max(0, canvasW - frameRight);
1081
1092
 
1082
1093
  const mask: RenderObjectSpec[] = [
1083
1094
  {
@@ -1102,7 +1113,7 @@ export class ImageTool implements Extension {
1102
1113
  data: { id: "image.cropMask.bottom", zIndex: 2 },
1103
1114
  props: {
1104
1115
  left: canvasW / 2,
1105
- top: frame.top + frame.height + bottomH / 2,
1116
+ top: frameBottom + bottomH / 2,
1106
1117
  width: canvasW,
1107
1118
  height: bottomH,
1108
1119
  originX: "center",
@@ -1118,9 +1129,9 @@ export class ImageTool implements Extension {
1118
1129
  data: { id: "image.cropMask.left", zIndex: 3 },
1119
1130
  props: {
1120
1131
  left: leftW / 2,
1121
- top: frame.top + frame.height / 2,
1132
+ top: frameTop + visibleFrameH / 2,
1122
1133
  width: leftW,
1123
- height: frame.height,
1134
+ height: visibleFrameH,
1124
1135
  originX: "center",
1125
1136
  originY: "center",
1126
1137
  fill: visual.outerBackground,
@@ -1133,10 +1144,10 @@ export class ImageTool implements Extension {
1133
1144
  type: "rect",
1134
1145
  data: { id: "image.cropMask.right", zIndex: 4 },
1135
1146
  props: {
1136
- left: frame.left + frame.width + rightW / 2,
1137
- top: frame.top + frame.height / 2,
1147
+ left: frameRight + rightW / 2,
1148
+ top: frameTop + visibleFrameH / 2,
1138
1149
  width: rightW,
1139
- height: frame.height,
1150
+ height: visibleFrameH,
1140
1151
  originX: "center",
1141
1152
  originY: "center",
1142
1153
  fill: visual.outerBackground,
@@ -1275,6 +1286,7 @@ export class ImageTool implements Extension {
1275
1286
  if (this.isToolActive) {
1276
1287
  this.updateImages();
1277
1288
  }
1289
+ this.emitWorkingChange(id);
1278
1290
  }
1279
1291
 
1280
1292
  private async updateImageInConfig(id: string, updates: Partial<ImageItem>) {
@@ -1371,6 +1383,7 @@ export class ImageTool implements Extension {
1371
1383
  this.isImageSelectionActive = true;
1372
1384
  this.focusedImageId = id;
1373
1385
  this.updateImages();
1386
+ this.emitWorkingChange(id);
1374
1387
  }
1375
1388
 
1376
1389
  private focusImageSelection(id: string) {
@@ -1489,6 +1502,7 @@ export class ImageTool implements Extension {
1489
1502
  this.hasWorkingChanges = false;
1490
1503
  this.workingItems = this.cloneItems(next);
1491
1504
  this.updateConfig(next);
1505
+ this.emitWorkingChange(focusId);
1492
1506
  if (focusId) {
1493
1507
  this.focusedImageId = focusId;
1494
1508
  this.isImageSelectionActive = true;