@pooder/kit 6.1.1 → 6.2.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.
@@ -6,17 +6,31 @@ import {
6
6
  ConfigurationService,
7
7
  ToolSessionService,
8
8
  } from "@pooder/core";
9
- import { CanvasService, RenderObjectSpec } from "../../services";
9
+ import { Pattern } from "fabric";
10
+ import {
11
+ CanvasService,
12
+ RenderEffectSpec,
13
+ RenderObjectSpec,
14
+ RenderPassSpec,
15
+ } from "../../services";
10
16
  import { resolveFeaturePosition } from "../geometry";
11
17
  import { ConstraintRegistry, ConstraintFeature } from "../constraints";
12
18
  import { completeFeaturesStrict } from "../featureComplete";
13
19
  import {
20
+ computeSceneLayout,
14
21
  readSizeState,
15
22
  type SceneGeometrySnapshot as DielineGeometry,
16
23
  } from "../../shared/scene/sceneLayoutModel";
17
- import { FEATURE_OVERLAY_LAYER_ID } from "../../shared/constants/layers";
24
+ import {
25
+ DIELINE_LAYER_ID,
26
+ FEATURE_DIELINE_LAYER_ID,
27
+ FEATURE_OVERLAY_LAYER_ID,
28
+ IMAGE_OBJECT_LAYER_ID,
29
+ } from "../../shared/constants/layers";
18
30
  import { SubscriptionBag } from "../../shared/runtime/subscriptions";
19
31
  import { cloneWithJson } from "../../shared/runtime/sessionState";
32
+ import { buildDielineRenderBundle } from "../dieline/renderBuilder";
33
+ import { readDielineState } from "../dieline/model";
20
34
  const FEATURE_STROKE_WIDTH = 2;
21
35
  const DEFAULT_RECT_SIZE = 10;
22
36
  const DEFAULT_CIRCLE_RADIUS = 5;
@@ -68,7 +82,9 @@ export class FeatureTool implements Extension {
68
82
  private hasWorkingChanges = false;
69
83
  private dirtyTrackerDisposable?: { dispose(): void };
70
84
  private renderProducerDisposable?: { dispose: () => void };
71
- private specs: RenderObjectSpec[] = [];
85
+ private markerSpecs: RenderObjectSpec[] = [];
86
+ private sessionDielineSpecs: RenderObjectSpec[] = [];
87
+ private sessionDielineEffects: RenderEffectSpec[] = [];
72
88
  private renderSeq = 0;
73
89
  private readonly subscriptions = new SubscriptionBag();
74
90
 
@@ -103,16 +119,38 @@ export class FeatureTool implements Extension {
103
119
  this.renderProducerDisposable?.dispose();
104
120
  this.renderProducerDisposable = this.canvasService.registerRenderProducer(
105
121
  this.id,
106
- () => ({
107
- passes: [
122
+ () => {
123
+ const passes: RenderPassSpec[] = [
108
124
  {
109
125
  id: FEATURE_OVERLAY_LAYER_ID,
110
126
  stack: 880,
111
127
  order: 0,
112
- objects: this.specs,
128
+ replace: true,
129
+ objects: this.markerSpecs,
113
130
  },
114
- ],
115
- }),
131
+ ];
132
+ if (this.isSessionVisible()) {
133
+ passes.push(
134
+ {
135
+ id: DIELINE_LAYER_ID,
136
+ stack: 700,
137
+ order: 0,
138
+ replace: false,
139
+ visibility: { op: "const", value: false },
140
+ objects: [],
141
+ },
142
+ {
143
+ id: FEATURE_DIELINE_LAYER_ID,
144
+ stack: 705,
145
+ order: 0,
146
+ replace: true,
147
+ effects: this.sessionDielineEffects,
148
+ objects: this.sessionDielineSpecs,
149
+ },
150
+ );
151
+ }
152
+ return { passes };
153
+ },
116
154
  { priority: 350 },
117
155
  );
118
156
 
@@ -131,12 +169,23 @@ export class FeatureTool implements Extension {
131
169
  if (this.isUpdatingConfig) return;
132
170
 
133
171
  if (e.key === "dieline.features") {
134
- if (this.isFeatureSessionActive) return;
172
+ if (this.isFeatureSessionActive && this.hasFeatureSessionDraft()) {
173
+ return;
174
+ }
175
+ if (this.hasFeatureSessionDraft()) {
176
+ this.clearFeatureSessionState();
177
+ }
135
178
  const next = (e.value || []) as ConstraintFeature[];
136
179
  this.workingFeatures = this.cloneFeatures(next);
137
180
  this.hasWorkingChanges = false;
138
181
  this.redraw();
139
182
  this.emitWorkingChange();
183
+ return;
184
+ }
185
+
186
+ if (e.key.startsWith("size.") || e.key.startsWith("dieline.")) {
187
+ void this.refreshGeometry();
188
+ this.redraw({ enforceConstraints: true });
140
189
  }
141
190
  },
142
191
  );
@@ -156,7 +205,8 @@ export class FeatureTool implements Extension {
156
205
 
157
206
  deactivate(context: ExtensionContext) {
158
207
  this.subscriptions.disposeAll();
159
- this.restoreSessionFeaturesToConfig();
208
+ this.restoreCommittedFeaturesToConfig();
209
+ this.clearFeatureSessionState();
160
210
  this.dirtyTrackerDisposable?.dispose();
161
211
  this.dirtyTrackerDisposable = undefined;
162
212
  this.teardown();
@@ -167,7 +217,7 @@ export class FeatureTool implements Extension {
167
217
  private onToolActivated = (event: { id: string | null }) => {
168
218
  this.isToolActive = event.id === this.id;
169
219
  if (!this.isToolActive) {
170
- this.restoreSessionFeaturesToConfig();
220
+ this.suspendFeatureSession();
171
221
  }
172
222
  this.updateVisibility();
173
223
  };
@@ -176,6 +226,10 @@ export class FeatureTool implements Extension {
176
226
  this.redraw();
177
227
  }
178
228
 
229
+ private isSessionVisible(): boolean {
230
+ return this.isToolActive && this.isFeatureSessionActive;
231
+ }
232
+
179
233
  contribute() {
180
234
  return {
181
235
  [ContributionPointIds.TOOLS]: [
@@ -202,15 +256,16 @@ export class FeatureTool implements Extension {
202
256
  if (this.isFeatureSessionActive) {
203
257
  return { ok: true };
204
258
  }
205
- const original = this.getCommittedFeatures();
206
- this.sessionOriginalFeatures = this.cloneFeatures(original);
259
+ if (!this.hasFeatureSessionDraft()) {
260
+ const original = this.getCommittedFeatures();
261
+ this.sessionOriginalFeatures = this.cloneFeatures(original);
262
+ this.setWorkingFeatures(this.cloneFeatures(original));
263
+ this.hasWorkingChanges = false;
264
+ }
207
265
  this.isFeatureSessionActive = true;
208
266
  await this.refreshGeometry();
209
- this.setWorkingFeatures(this.cloneFeatures(original));
210
- this.hasWorkingChanges = false;
211
267
  this.redraw();
212
268
  this.emitWorkingChange();
213
- this.updateCommittedFeatures([]);
214
269
  return { ok: true };
215
270
  },
216
271
  },
@@ -246,25 +301,6 @@ export class FeatureTool implements Extension {
246
301
  return true;
247
302
  },
248
303
  },
249
- {
250
- command: "getWorkingFeatures",
251
- title: "Get Working Features",
252
- handler: () => {
253
- return this.cloneFeatures(this.workingFeatures);
254
- },
255
- },
256
- {
257
- command: "setWorkingFeatures",
258
- title: "Set Working Features",
259
- handler: async (features: ConstraintFeature[]) => {
260
- await this.refreshGeometry();
261
- this.setWorkingFeatures(this.cloneFeatures(features || []));
262
- this.hasWorkingChanges = true;
263
- this.redraw();
264
- this.emitWorkingChange();
265
- return { ok: true };
266
- },
267
- },
268
304
  {
269
305
  command: "rollbackFeatureSession",
270
306
  title: "Rollback Feature Session",
@@ -336,18 +372,27 @@ export class FeatureTool implements Extension {
336
372
  }
337
373
  }
338
374
 
375
+ private hasFeatureSessionDraft(): boolean {
376
+ return Array.isArray(this.sessionOriginalFeatures);
377
+ }
378
+
339
379
  private clearFeatureSessionState() {
340
380
  this.isFeatureSessionActive = false;
341
381
  this.sessionOriginalFeatures = null;
342
382
  }
343
383
 
344
- private restoreSessionFeaturesToConfig() {
345
- if (!this.isFeatureSessionActive) return;
384
+ private restoreCommittedFeaturesToConfig() {
385
+ if (!this.hasFeatureSessionDraft()) return;
346
386
  const original = this.cloneFeatures(
347
387
  this.sessionOriginalFeatures || this.getCommittedFeatures(),
348
388
  );
349
389
  this.updateCommittedFeatures(original);
350
- this.clearFeatureSessionState();
390
+ }
391
+
392
+ private suspendFeatureSession() {
393
+ if (!this.isFeatureSessionActive) return;
394
+ this.restoreCommittedFeaturesToConfig();
395
+ this.isFeatureSessionActive = false;
351
396
  }
352
397
 
353
398
  private emitWorkingChange() {
@@ -370,9 +415,7 @@ export class FeatureTool implements Extension {
370
415
 
371
416
  private async resetWorkingFeaturesFromSource() {
372
417
  const next = this.cloneFeatures(
373
- this.isFeatureSessionActive && this.sessionOriginalFeatures
374
- ? this.sessionOriginalFeatures
375
- : this.getCommittedFeatures(),
418
+ this.sessionOriginalFeatures || this.getCommittedFeatures(),
376
419
  );
377
420
  await this.refreshGeometry();
378
421
  this.setWorkingFeatures(next);
@@ -636,12 +679,37 @@ export class FeatureTool implements Extension {
636
679
  }
637
680
 
638
681
  this.renderSeq += 1;
639
- this.specs = [];
682
+ this.markerSpecs = [];
683
+ this.sessionDielineSpecs = [];
684
+ this.sessionDielineEffects = [];
640
685
  this.renderProducerDisposable?.dispose();
641
686
  this.renderProducerDisposable = undefined;
642
687
  void this.canvasService.flushRenderFromProducers();
643
688
  }
644
689
 
690
+ private createHatchPattern(color: string = "rgba(0, 0, 0, 0.3)") {
691
+ if (typeof document === "undefined") {
692
+ return undefined;
693
+ }
694
+ const size = 20;
695
+ const canvas = document.createElement("canvas");
696
+ canvas.width = size;
697
+ canvas.height = size;
698
+ const ctx = canvas.getContext("2d");
699
+ if (ctx) {
700
+ ctx.clearRect(0, 0, size, size);
701
+ ctx.strokeStyle = color;
702
+ ctx.lineWidth = 1;
703
+ ctx.beginPath();
704
+ ctx.moveTo(0, size);
705
+ ctx.lineTo(size, 0);
706
+ ctx.stroke();
707
+ }
708
+ return new Pattern({
709
+ source: canvas,
710
+ } as any);
711
+ }
712
+
645
713
  private getDraggableMarkerTarget(target: any): any | null {
646
714
  if (!this.isFeatureSessionActive || !this.isToolActive) return null;
647
715
  if (!target || target.data?.type !== "feature-marker") return null;
@@ -737,6 +805,7 @@ export class FeatureTool implements Extension {
737
805
  next[index] = updatedFeature;
738
806
  this.setWorkingFeatures(next);
739
807
  this.hasWorkingChanges = true;
808
+ this.redraw();
740
809
  this.emitWorkingChange();
741
810
  }
742
811
 
@@ -780,6 +849,7 @@ export class FeatureTool implements Extension {
780
849
  if (!changed) return;
781
850
  this.setWorkingFeatures(next);
782
851
  this.hasWorkingChanges = true;
852
+ this.redraw();
783
853
  this.emitWorkingChange();
784
854
  }
785
855
 
@@ -791,7 +861,10 @@ export class FeatureTool implements Extension {
791
861
  if (!this.canvasService) return;
792
862
 
793
863
  const seq = ++this.renderSeq;
794
- this.specs = this.buildFeatureSpecs();
864
+ this.markerSpecs = this.buildMarkerSpecs();
865
+ const sessionRender = this.buildSessionDielineRender();
866
+ this.sessionDielineSpecs = sessionRender.specs;
867
+ this.sessionDielineEffects = sessionRender.effects;
795
868
  if (seq !== this.renderSeq) return;
796
869
 
797
870
  await this.canvasService.flushRenderFromProducers();
@@ -801,7 +874,57 @@ export class FeatureTool implements Extension {
801
874
  }
802
875
  }
803
876
 
804
- private buildFeatureSpecs(): RenderObjectSpec[] {
877
+ private buildSessionDielineRender(): {
878
+ specs: RenderObjectSpec[];
879
+ effects: RenderEffectSpec[];
880
+ } {
881
+ if (!this.isSessionVisible() || !this.canvasService) {
882
+ return { specs: [], effects: [] };
883
+ }
884
+ const configService = this.getConfigService();
885
+ if (!configService) {
886
+ return { specs: [], effects: [] };
887
+ }
888
+ const sceneLayout = computeSceneLayout(
889
+ this.canvasService,
890
+ readSizeState(configService),
891
+ );
892
+ if (!sceneLayout) {
893
+ return { specs: [], effects: [] };
894
+ }
895
+
896
+ const state = readDielineState(configService);
897
+ state.features = this.cloneFeatures(this.workingFeatures);
898
+
899
+ return buildDielineRenderBundle({
900
+ state,
901
+ sceneLayout,
902
+ canvasWidth: sceneLayout.canvasWidth || this.canvasService.canvas.width || 800,
903
+ canvasHeight:
904
+ sceneLayout.canvasHeight || this.canvasService.canvas.height || 600,
905
+ hasImages: this.hasImageItems(),
906
+ createHatchPattern: (color) => this.createHatchPattern(color),
907
+ clipTargetPassIds: [IMAGE_OBJECT_LAYER_ID],
908
+ clipVisibility: { op: "const", value: true },
909
+ ids: {
910
+ inside: "feature.session.dieline.inside",
911
+ bleedZone: "feature.session.dieline.bleed-zone",
912
+ offsetBorder: "feature.session.dieline.offset-border",
913
+ border: "feature.session.dieline.border",
914
+ clip: "feature.session.dieline.clip.image",
915
+ clipSource: "feature.session.dieline.effect.clip-path",
916
+ },
917
+ });
918
+ }
919
+
920
+ private hasImageItems(): boolean {
921
+ const configService = this.getConfigService();
922
+ if (!configService) return false;
923
+ const items = configService.get("image.items", []) as unknown;
924
+ return Array.isArray(items) && items.length > 0;
925
+ }
926
+
927
+ private buildMarkerSpecs(): RenderObjectSpec[] {
805
928
  if (
806
929
  !this.isFeatureSessionActive ||
807
930
  !this.currentGeometry ||