@pooder/kit 4.3.1 → 5.0.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/.test-dist/src/CanvasService.js +249 -0
- package/.test-dist/src/ViewportSystem.js +75 -0
- package/.test-dist/src/background.js +203 -0
- package/.test-dist/src/bridgeSelection.js +20 -0
- package/.test-dist/src/constraints.js +237 -0
- package/.test-dist/src/coordinate.js +74 -0
- package/.test-dist/src/dieline.js +818 -0
- package/.test-dist/src/edgeScale.js +12 -0
- package/.test-dist/src/feature.js +754 -0
- package/.test-dist/src/featureComplete.js +32 -0
- package/.test-dist/src/film.js +167 -0
- package/.test-dist/src/geometry.js +506 -0
- package/.test-dist/src/image.js +1234 -0
- package/.test-dist/src/index.js +35 -0
- package/.test-dist/src/maskOps.js +270 -0
- package/.test-dist/src/mirror.js +104 -0
- package/.test-dist/src/renderSpec.js +2 -0
- package/.test-dist/src/ruler.js +343 -0
- package/.test-dist/src/sceneLayout.js +99 -0
- package/.test-dist/src/sceneLayoutModel.js +196 -0
- package/.test-dist/src/sceneView.js +40 -0
- package/.test-dist/src/sceneVisibility.js +42 -0
- package/.test-dist/src/size.js +332 -0
- package/.test-dist/src/tracer.js +544 -0
- package/.test-dist/src/units.js +30 -0
- package/.test-dist/src/white-ink.js +829 -0
- package/.test-dist/src/wrappedOffsets.js +33 -0
- package/.test-dist/tests/run.js +94 -0
- package/CHANGELOG.md +17 -0
- package/dist/index.d.mts +342 -37
- package/dist/index.d.ts +342 -37
- package/dist/index.js +3679 -865
- package/dist/index.mjs +3673 -868
- package/package.json +2 -2
- package/src/CanvasService.ts +300 -96
- package/src/ViewportSystem.ts +92 -92
- package/src/background.ts +230 -230
- package/src/bridgeSelection.ts +17 -0
- package/src/coordinate.ts +106 -106
- package/src/dieline.ts +1005 -973
- package/src/edgeScale.ts +19 -0
- package/src/feature.ts +83 -30
- package/src/film.ts +194 -194
- package/src/geometry.ts +242 -84
- package/src/image.ts +1582 -512
- package/src/index.ts +14 -10
- package/src/maskOps.ts +326 -0
- package/src/mirror.ts +128 -128
- package/src/renderSpec.ts +18 -0
- package/src/ruler.ts +449 -508
- package/src/sceneLayout.ts +121 -0
- package/src/sceneLayoutModel.ts +335 -0
- package/src/sceneVisibility.ts +49 -0
- package/src/size.ts +379 -0
- package/src/tracer.ts +719 -570
- package/src/units.ts +27 -27
- package/src/white-ink.ts +1018 -373
- package/src/wrappedOffsets.ts +33 -0
- package/tests/run.ts +118 -0
- package/tsconfig.test.json +15 -15
package/src/edgeScale.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface BoundsLike {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function computeDetectEdgeSize(
|
|
7
|
+
currentMax: number,
|
|
8
|
+
baseBounds: BoundsLike,
|
|
9
|
+
expandedBounds: BoundsLike,
|
|
10
|
+
): { width: number; height: number; scale: number } {
|
|
11
|
+
const baseMax = Math.max(baseBounds.width, baseBounds.height);
|
|
12
|
+
const scale = baseMax > 0 ? currentMax / baseMax : 1;
|
|
13
|
+
return {
|
|
14
|
+
scale,
|
|
15
|
+
width: expandedBounds.width * scale,
|
|
16
|
+
height: expandedBounds.height * scale,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
package/src/feature.ts
CHANGED
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
ContributionPointIds,
|
|
5
5
|
CommandContribution,
|
|
6
6
|
ConfigurationService,
|
|
7
|
+
ToolSessionService,
|
|
7
8
|
} from "@pooder/core";
|
|
8
9
|
import { Circle, Group, Point, Rect } from "fabric";
|
|
9
10
|
import CanvasService from "./CanvasService";
|
|
10
|
-
import { DielineGeometry } from "./dieline";
|
|
11
11
|
import {
|
|
12
12
|
getNearestPointOnDieline,
|
|
13
13
|
DielineFeature,
|
|
@@ -17,7 +17,10 @@ import { ConstraintRegistry, ConstraintFeature } from "./constraints";
|
|
|
17
17
|
import {
|
|
18
18
|
completeFeaturesStrict,
|
|
19
19
|
} from "./featureComplete";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
readSizeState,
|
|
22
|
+
type SceneGeometrySnapshot as DielineGeometry,
|
|
23
|
+
} from "./sceneLayoutModel";
|
|
21
24
|
|
|
22
25
|
export class FeatureTool implements Extension {
|
|
23
26
|
id = "pooder.kit.feature";
|
|
@@ -31,10 +34,12 @@ export class FeatureTool implements Extension {
|
|
|
31
34
|
private context?: ExtensionContext;
|
|
32
35
|
private isUpdatingConfig = false;
|
|
33
36
|
private isToolActive = false;
|
|
37
|
+
private hasWorkingChanges = false;
|
|
38
|
+
private dirtyTrackerDisposable?: { dispose(): void };
|
|
34
39
|
|
|
35
40
|
private handleMoving: ((e: any) => void) | null = null;
|
|
36
41
|
private handleModified: ((e: any) => void) | null = null;
|
|
37
|
-
private
|
|
42
|
+
private handleSceneGeometryChange: ((geometry: DielineGeometry) => void) | null =
|
|
38
43
|
null;
|
|
39
44
|
|
|
40
45
|
private currentGeometry: DielineGeometry | null = null;
|
|
@@ -65,6 +70,7 @@ export class FeatureTool implements Extension {
|
|
|
65
70
|
const features = (configService.get("dieline.features", []) ||
|
|
66
71
|
[]) as ConstraintFeature[];
|
|
67
72
|
this.workingFeatures = this.cloneFeatures(features);
|
|
73
|
+
this.hasWorkingChanges = false;
|
|
68
74
|
|
|
69
75
|
configService.onAnyChange((e: { key: string; value: any }) => {
|
|
70
76
|
if (this.isUpdatingConfig) return;
|
|
@@ -72,12 +78,20 @@ export class FeatureTool implements Extension {
|
|
|
72
78
|
if (e.key === "dieline.features") {
|
|
73
79
|
const next = (e.value || []) as ConstraintFeature[];
|
|
74
80
|
this.workingFeatures = this.cloneFeatures(next);
|
|
81
|
+
this.hasWorkingChanges = false;
|
|
75
82
|
this.redraw();
|
|
76
83
|
this.emitWorkingChange();
|
|
77
84
|
}
|
|
78
85
|
});
|
|
79
86
|
}
|
|
80
87
|
|
|
88
|
+
const toolSessionService =
|
|
89
|
+
context.services.get<ToolSessionService>("ToolSessionService");
|
|
90
|
+
this.dirtyTrackerDisposable = toolSessionService?.registerDirtyTracker(
|
|
91
|
+
this.id,
|
|
92
|
+
() => this.hasWorkingChanges,
|
|
93
|
+
);
|
|
94
|
+
|
|
81
95
|
// Listen to tool activation
|
|
82
96
|
context.eventBus.on("tool:activated", this.onToolActivated);
|
|
83
97
|
|
|
@@ -86,6 +100,8 @@ export class FeatureTool implements Extension {
|
|
|
86
100
|
|
|
87
101
|
deactivate(context: ExtensionContext) {
|
|
88
102
|
context.eventBus.off("tool:activated", this.onToolActivated);
|
|
103
|
+
this.dirtyTrackerDisposable?.dispose();
|
|
104
|
+
this.dirtyTrackerDisposable = undefined;
|
|
89
105
|
this.teardown();
|
|
90
106
|
this.canvasService = undefined;
|
|
91
107
|
this.context = undefined;
|
|
@@ -118,6 +134,22 @@ export class FeatureTool implements Extension {
|
|
|
118
134
|
|
|
119
135
|
contribute() {
|
|
120
136
|
return {
|
|
137
|
+
[ContributionPointIds.TOOLS]: [
|
|
138
|
+
{
|
|
139
|
+
id: this.id,
|
|
140
|
+
name: "Feature",
|
|
141
|
+
interaction: "session",
|
|
142
|
+
commands: {
|
|
143
|
+
begin: "resetWorkingFeatures",
|
|
144
|
+
commit: "completeFeatures",
|
|
145
|
+
rollback: "resetWorkingFeatures",
|
|
146
|
+
},
|
|
147
|
+
session: {
|
|
148
|
+
autoBegin: false,
|
|
149
|
+
leavePolicy: "block",
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
],
|
|
121
153
|
[ContributionPointIds.COMMANDS]: [
|
|
122
154
|
{
|
|
123
155
|
command: "addFeature",
|
|
@@ -145,6 +177,7 @@ export class FeatureTool implements Extension {
|
|
|
145
177
|
title: "Clear Features",
|
|
146
178
|
handler: () => {
|
|
147
179
|
this.setWorkingFeatures([]);
|
|
180
|
+
this.hasWorkingChanges = true;
|
|
148
181
|
this.redraw();
|
|
149
182
|
this.emitWorkingChange();
|
|
150
183
|
return true;
|
|
@@ -163,6 +196,26 @@ export class FeatureTool implements Extension {
|
|
|
163
196
|
handler: async (features: ConstraintFeature[]) => {
|
|
164
197
|
await this.refreshGeometry();
|
|
165
198
|
this.setWorkingFeatures(this.cloneFeatures(features || []));
|
|
199
|
+
this.hasWorkingChanges = true;
|
|
200
|
+
this.redraw();
|
|
201
|
+
this.emitWorkingChange();
|
|
202
|
+
return { ok: true };
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
command: "resetWorkingFeatures",
|
|
207
|
+
title: "Reset Working Features",
|
|
208
|
+
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
|
+
|
|
216
|
+
await this.refreshGeometry();
|
|
217
|
+
this.setWorkingFeatures(this.cloneFeatures(next));
|
|
218
|
+
this.hasWorkingChanges = false;
|
|
166
219
|
this.redraw();
|
|
167
220
|
this.emitWorkingChange();
|
|
168
221
|
return { ok: true };
|
|
@@ -201,7 +254,9 @@ export class FeatureTool implements Extension {
|
|
|
201
254
|
const commandService = this.context.services.get<any>("CommandService");
|
|
202
255
|
if (!commandService) return;
|
|
203
256
|
try {
|
|
204
|
-
const g = await Promise.resolve(
|
|
257
|
+
const g = await Promise.resolve(
|
|
258
|
+
commandService.executeCommand("getSceneGeometry"),
|
|
259
|
+
);
|
|
205
260
|
if (g) this.currentGeometry = g as DielineGeometry;
|
|
206
261
|
} catch (e) {}
|
|
207
262
|
}
|
|
@@ -217,14 +272,9 @@ export class FeatureTool implements Extension {
|
|
|
217
272
|
this.context?.services.get<ConfigurationService>("ConfigurationService");
|
|
218
273
|
if (!configService) return { ok: false };
|
|
219
274
|
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
);
|
|
224
|
-
const dielineHeight = parseLengthToMm(
|
|
225
|
-
configService.get("dieline.height") ?? 500,
|
|
226
|
-
"mm",
|
|
227
|
-
);
|
|
275
|
+
const sizeState = readSizeState(configService);
|
|
276
|
+
const dielineWidth = sizeState.actualWidthMm;
|
|
277
|
+
const dielineHeight = sizeState.actualHeightMm;
|
|
228
278
|
|
|
229
279
|
let changed = false;
|
|
230
280
|
const next = this.workingFeatures.map((f) => {
|
|
@@ -250,6 +300,7 @@ export class FeatureTool implements Extension {
|
|
|
250
300
|
if (!changed) return { ok: true };
|
|
251
301
|
|
|
252
302
|
this.setWorkingFeatures(next);
|
|
303
|
+
this.hasWorkingChanges = true;
|
|
253
304
|
this.redraw();
|
|
254
305
|
this.enforceConstraints();
|
|
255
306
|
this.emitWorkingChange();
|
|
@@ -276,14 +327,9 @@ export class FeatureTool implements Extension {
|
|
|
276
327
|
};
|
|
277
328
|
}
|
|
278
329
|
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
);
|
|
283
|
-
const dielineHeight = parseLengthToMm(
|
|
284
|
-
configService.get("dieline.height") ?? 500,
|
|
285
|
-
"mm",
|
|
286
|
-
);
|
|
330
|
+
const sizeState = readSizeState(configService);
|
|
331
|
+
const dielineWidth = sizeState.actualWidthMm;
|
|
332
|
+
const dielineHeight = sizeState.actualHeightMm;
|
|
287
333
|
|
|
288
334
|
const result = completeFeaturesStrict(
|
|
289
335
|
this.workingFeatures,
|
|
@@ -308,6 +354,9 @@ export class FeatureTool implements Extension {
|
|
|
308
354
|
};
|
|
309
355
|
}
|
|
310
356
|
|
|
357
|
+
this.hasWorkingChanges = false;
|
|
358
|
+
// Keep feature markers above dieline overlay after config-driven redraw.
|
|
359
|
+
this.redraw();
|
|
311
360
|
return { ok: true };
|
|
312
361
|
}
|
|
313
362
|
|
|
@@ -330,6 +379,7 @@ export class FeatureTool implements Extension {
|
|
|
330
379
|
};
|
|
331
380
|
|
|
332
381
|
this.setWorkingFeatures([...(this.workingFeatures || []), newFeature]);
|
|
382
|
+
this.hasWorkingChanges = true;
|
|
333
383
|
this.redraw();
|
|
334
384
|
this.emitWorkingChange();
|
|
335
385
|
return true;
|
|
@@ -370,6 +420,7 @@ export class FeatureTool implements Extension {
|
|
|
370
420
|
};
|
|
371
421
|
|
|
372
422
|
this.setWorkingFeatures([...(this.workingFeatures || []), lug, hole]);
|
|
423
|
+
this.hasWorkingChanges = true;
|
|
373
424
|
this.redraw();
|
|
374
425
|
this.emitWorkingChange();
|
|
375
426
|
return true;
|
|
@@ -388,16 +439,16 @@ export class FeatureTool implements Extension {
|
|
|
388
439
|
if (!this.canvasService || !this.context) return;
|
|
389
440
|
const canvas = this.canvasService.canvas;
|
|
390
441
|
|
|
391
|
-
// 1. Listen for
|
|
392
|
-
if (!this.
|
|
393
|
-
this.
|
|
442
|
+
// 1. Listen for Scene Geometry Changes
|
|
443
|
+
if (!this.handleSceneGeometryChange) {
|
|
444
|
+
this.handleSceneGeometryChange = (geometry: DielineGeometry) => {
|
|
394
445
|
this.currentGeometry = geometry;
|
|
395
446
|
this.redraw();
|
|
396
447
|
this.enforceConstraints();
|
|
397
448
|
};
|
|
398
449
|
this.context.eventBus.on(
|
|
399
|
-
"
|
|
400
|
-
this.
|
|
450
|
+
"scene:geometry:change",
|
|
451
|
+
this.handleSceneGeometryChange,
|
|
401
452
|
);
|
|
402
453
|
}
|
|
403
454
|
|
|
@@ -405,7 +456,7 @@ export class FeatureTool implements Extension {
|
|
|
405
456
|
const commandService = this.context.services.get<any>("CommandService");
|
|
406
457
|
if (commandService) {
|
|
407
458
|
try {
|
|
408
|
-
Promise.resolve(commandService.executeCommand("
|
|
459
|
+
Promise.resolve(commandService.executeCommand("getSceneGeometry")).then(
|
|
409
460
|
(g) => {
|
|
410
461
|
if (g) {
|
|
411
462
|
this.currentGeometry = g as DielineGeometry;
|
|
@@ -521,6 +572,7 @@ export class FeatureTool implements Extension {
|
|
|
521
572
|
});
|
|
522
573
|
|
|
523
574
|
this.setWorkingFeatures(newFeatures);
|
|
575
|
+
this.hasWorkingChanges = true;
|
|
524
576
|
this.emitWorkingChange();
|
|
525
577
|
} else {
|
|
526
578
|
// Single object
|
|
@@ -543,12 +595,12 @@ export class FeatureTool implements Extension {
|
|
|
543
595
|
canvas.off("object:modified", this.handleModified);
|
|
544
596
|
this.handleModified = null;
|
|
545
597
|
}
|
|
546
|
-
if (this.
|
|
598
|
+
if (this.handleSceneGeometryChange && this.context) {
|
|
547
599
|
this.context.eventBus.off(
|
|
548
|
-
"
|
|
549
|
-
this.
|
|
600
|
+
"scene:geometry:change",
|
|
601
|
+
this.handleSceneGeometryChange,
|
|
550
602
|
);
|
|
551
|
-
this.
|
|
603
|
+
this.handleSceneGeometryChange = null;
|
|
552
604
|
}
|
|
553
605
|
|
|
554
606
|
const objects = canvas
|
|
@@ -636,6 +688,7 @@ export class FeatureTool implements Extension {
|
|
|
636
688
|
const newFeatures = [...this.workingFeatures];
|
|
637
689
|
newFeatures[index] = updatedFeature;
|
|
638
690
|
this.setWorkingFeatures(newFeatures);
|
|
691
|
+
this.hasWorkingChanges = true;
|
|
639
692
|
this.emitWorkingChange();
|
|
640
693
|
}
|
|
641
694
|
|
package/src/film.ts
CHANGED
|
@@ -1,194 +1,194 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Extension,
|
|
3
|
-
ExtensionContext,
|
|
4
|
-
ContributionPointIds,
|
|
5
|
-
CommandContribution,
|
|
6
|
-
ConfigurationContribution,
|
|
7
|
-
} from "@pooder/core";
|
|
8
|
-
import { FabricImage as Image } from "fabric";
|
|
9
|
-
import CanvasService from "./CanvasService";
|
|
10
|
-
|
|
11
|
-
export class FilmTool implements Extension {
|
|
12
|
-
id = "pooder.kit.film";
|
|
13
|
-
|
|
14
|
-
public metadata = {
|
|
15
|
-
name: "FilmTool",
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
private url: string = "";
|
|
19
|
-
private opacity: number = 0.5;
|
|
20
|
-
|
|
21
|
-
private canvasService?: CanvasService;
|
|
22
|
-
|
|
23
|
-
constructor(
|
|
24
|
-
options?: Partial<{
|
|
25
|
-
url: string;
|
|
26
|
-
opacity: number;
|
|
27
|
-
}>,
|
|
28
|
-
) {
|
|
29
|
-
if (options) {
|
|
30
|
-
Object.assign(this, options);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
activate(context: ExtensionContext) {
|
|
35
|
-
this.canvasService = context.services.get<CanvasService>("CanvasService");
|
|
36
|
-
if (!this.canvasService) {
|
|
37
|
-
console.warn("CanvasService not found for FilmTool");
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const configService = context.services.get<any>("ConfigurationService");
|
|
42
|
-
if (configService) {
|
|
43
|
-
// Load initial config
|
|
44
|
-
this.url = configService.get("film.url", this.url);
|
|
45
|
-
this.opacity = configService.get("film.opacity", this.opacity);
|
|
46
|
-
|
|
47
|
-
// Listen for changes
|
|
48
|
-
configService.onAnyChange((e: { key: string; value: any }) => {
|
|
49
|
-
if (e.key.startsWith("film.")) {
|
|
50
|
-
const prop = e.key.split(".")[1];
|
|
51
|
-
console.log(
|
|
52
|
-
`[FilmTool] Config change detected: ${e.key} -> ${e.value}`,
|
|
53
|
-
);
|
|
54
|
-
if (prop && prop in this) {
|
|
55
|
-
(this as any)[prop] = e.value;
|
|
56
|
-
this.updateFilm();
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
this.initLayer();
|
|
63
|
-
this.updateFilm();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
deactivate(context: ExtensionContext) {
|
|
67
|
-
if (this.canvasService) {
|
|
68
|
-
const layer = this.canvasService.getLayer("overlay");
|
|
69
|
-
if (layer) {
|
|
70
|
-
const img = this.canvasService.getObject("film-image", "overlay");
|
|
71
|
-
if (img) {
|
|
72
|
-
layer.remove(img);
|
|
73
|
-
this.canvasService.requestRenderAll();
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
this.canvasService = undefined;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
contribute() {
|
|
81
|
-
return {
|
|
82
|
-
[ContributionPointIds.CONFIGURATIONS]: [
|
|
83
|
-
{
|
|
84
|
-
id: "film.url",
|
|
85
|
-
type: "string",
|
|
86
|
-
label: "Film Image URL",
|
|
87
|
-
default: "",
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
id: "film.opacity",
|
|
91
|
-
type: "number",
|
|
92
|
-
label: "Opacity",
|
|
93
|
-
min: 0,
|
|
94
|
-
max: 1,
|
|
95
|
-
step: 0.1,
|
|
96
|
-
default: 0.5,
|
|
97
|
-
},
|
|
98
|
-
] as ConfigurationContribution[],
|
|
99
|
-
[ContributionPointIds.COMMANDS]: [
|
|
100
|
-
{
|
|
101
|
-
command: "setFilmImage",
|
|
102
|
-
title: "Set Film Image",
|
|
103
|
-
handler: (url: string, opacity: number) => {
|
|
104
|
-
if (this.url === url && this.opacity === opacity) return true;
|
|
105
|
-
|
|
106
|
-
this.url = url;
|
|
107
|
-
this.opacity = opacity;
|
|
108
|
-
|
|
109
|
-
this.updateFilm();
|
|
110
|
-
|
|
111
|
-
return true;
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
] as CommandContribution[],
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private initLayer() {
|
|
119
|
-
if (!this.canvasService) return;
|
|
120
|
-
let overlayLayer = this.canvasService.getLayer("overlay");
|
|
121
|
-
if (!overlayLayer) {
|
|
122
|
-
const width = this.canvasService.canvas.width || 800;
|
|
123
|
-
const height = this.canvasService.canvas.height || 600;
|
|
124
|
-
|
|
125
|
-
const layer = this.canvasService.createLayer("overlay", {
|
|
126
|
-
width,
|
|
127
|
-
height,
|
|
128
|
-
left: 0,
|
|
129
|
-
top: 0,
|
|
130
|
-
originX: "left",
|
|
131
|
-
originY: "top",
|
|
132
|
-
selectable: false,
|
|
133
|
-
evented: false,
|
|
134
|
-
subTargetCheck: false,
|
|
135
|
-
interactive: false,
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
this.canvasService.canvas.bringObjectToFront(layer);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
private async updateFilm() {
|
|
143
|
-
if (!this.canvasService) return;
|
|
144
|
-
const layer = this.canvasService.getLayer("overlay");
|
|
145
|
-
if (!layer) {
|
|
146
|
-
console.warn("[FilmTool] Overlay layer not found");
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const { url, opacity } = this;
|
|
151
|
-
|
|
152
|
-
if (!url) {
|
|
153
|
-
const img = this.canvasService.getObject("film-image", "overlay");
|
|
154
|
-
if (img) {
|
|
155
|
-
layer.remove(img);
|
|
156
|
-
this.canvasService.requestRenderAll();
|
|
157
|
-
}
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const width = this.canvasService.canvas.width || 800;
|
|
162
|
-
const height = this.canvasService.canvas.height || 600;
|
|
163
|
-
|
|
164
|
-
let img = this.canvasService.getObject("film-image", "overlay") as Image;
|
|
165
|
-
try {
|
|
166
|
-
if (img) {
|
|
167
|
-
if (img.getSrc() !== url) {
|
|
168
|
-
await img.setSrc(url);
|
|
169
|
-
}
|
|
170
|
-
img.set({ opacity });
|
|
171
|
-
} else {
|
|
172
|
-
img = await Image.fromURL(url, { crossOrigin: "anonymous" });
|
|
173
|
-
img.scaleToWidth(width);
|
|
174
|
-
if (img.getScaledHeight() < height) img.scaleToHeight(height);
|
|
175
|
-
img.set({
|
|
176
|
-
originX: "left",
|
|
177
|
-
originY: "top",
|
|
178
|
-
left: 0,
|
|
179
|
-
top: 0,
|
|
180
|
-
opacity,
|
|
181
|
-
selectable: false,
|
|
182
|
-
evented: false,
|
|
183
|
-
data: { id: "film-image" },
|
|
184
|
-
});
|
|
185
|
-
layer.add(img);
|
|
186
|
-
}
|
|
187
|
-
this.canvasService.requestRenderAll();
|
|
188
|
-
} catch (error) {
|
|
189
|
-
console.error("[FilmTool] Failed to load film image", url, error);
|
|
190
|
-
}
|
|
191
|
-
layer.dirty = true;
|
|
192
|
-
this.canvasService.requestRenderAll();
|
|
193
|
-
}
|
|
194
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
Extension,
|
|
3
|
+
ExtensionContext,
|
|
4
|
+
ContributionPointIds,
|
|
5
|
+
CommandContribution,
|
|
6
|
+
ConfigurationContribution,
|
|
7
|
+
} from "@pooder/core";
|
|
8
|
+
import { FabricImage as Image } from "fabric";
|
|
9
|
+
import CanvasService from "./CanvasService";
|
|
10
|
+
|
|
11
|
+
export class FilmTool implements Extension {
|
|
12
|
+
id = "pooder.kit.film";
|
|
13
|
+
|
|
14
|
+
public metadata = {
|
|
15
|
+
name: "FilmTool",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
private url: string = "";
|
|
19
|
+
private opacity: number = 0.5;
|
|
20
|
+
|
|
21
|
+
private canvasService?: CanvasService;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
options?: Partial<{
|
|
25
|
+
url: string;
|
|
26
|
+
opacity: number;
|
|
27
|
+
}>,
|
|
28
|
+
) {
|
|
29
|
+
if (options) {
|
|
30
|
+
Object.assign(this, options);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
activate(context: ExtensionContext) {
|
|
35
|
+
this.canvasService = context.services.get<CanvasService>("CanvasService");
|
|
36
|
+
if (!this.canvasService) {
|
|
37
|
+
console.warn("CanvasService not found for FilmTool");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const configService = context.services.get<any>("ConfigurationService");
|
|
42
|
+
if (configService) {
|
|
43
|
+
// Load initial config
|
|
44
|
+
this.url = configService.get("film.url", this.url);
|
|
45
|
+
this.opacity = configService.get("film.opacity", this.opacity);
|
|
46
|
+
|
|
47
|
+
// Listen for changes
|
|
48
|
+
configService.onAnyChange((e: { key: string; value: any }) => {
|
|
49
|
+
if (e.key.startsWith("film.")) {
|
|
50
|
+
const prop = e.key.split(".")[1];
|
|
51
|
+
console.log(
|
|
52
|
+
`[FilmTool] Config change detected: ${e.key} -> ${e.value}`,
|
|
53
|
+
);
|
|
54
|
+
if (prop && prop in this) {
|
|
55
|
+
(this as any)[prop] = e.value;
|
|
56
|
+
this.updateFilm();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.initLayer();
|
|
63
|
+
this.updateFilm();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
deactivate(context: ExtensionContext) {
|
|
67
|
+
if (this.canvasService) {
|
|
68
|
+
const layer = this.canvasService.getLayer("overlay");
|
|
69
|
+
if (layer) {
|
|
70
|
+
const img = this.canvasService.getObject("film-image", "overlay");
|
|
71
|
+
if (img) {
|
|
72
|
+
layer.remove(img);
|
|
73
|
+
this.canvasService.requestRenderAll();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
this.canvasService = undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
contribute() {
|
|
81
|
+
return {
|
|
82
|
+
[ContributionPointIds.CONFIGURATIONS]: [
|
|
83
|
+
{
|
|
84
|
+
id: "film.url",
|
|
85
|
+
type: "string",
|
|
86
|
+
label: "Film Image URL",
|
|
87
|
+
default: "",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "film.opacity",
|
|
91
|
+
type: "number",
|
|
92
|
+
label: "Opacity",
|
|
93
|
+
min: 0,
|
|
94
|
+
max: 1,
|
|
95
|
+
step: 0.1,
|
|
96
|
+
default: 0.5,
|
|
97
|
+
},
|
|
98
|
+
] as ConfigurationContribution[],
|
|
99
|
+
[ContributionPointIds.COMMANDS]: [
|
|
100
|
+
{
|
|
101
|
+
command: "setFilmImage",
|
|
102
|
+
title: "Set Film Image",
|
|
103
|
+
handler: (url: string, opacity: number) => {
|
|
104
|
+
if (this.url === url && this.opacity === opacity) return true;
|
|
105
|
+
|
|
106
|
+
this.url = url;
|
|
107
|
+
this.opacity = opacity;
|
|
108
|
+
|
|
109
|
+
this.updateFilm();
|
|
110
|
+
|
|
111
|
+
return true;
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
] as CommandContribution[],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private initLayer() {
|
|
119
|
+
if (!this.canvasService) return;
|
|
120
|
+
let overlayLayer = this.canvasService.getLayer("overlay");
|
|
121
|
+
if (!overlayLayer) {
|
|
122
|
+
const width = this.canvasService.canvas.width || 800;
|
|
123
|
+
const height = this.canvasService.canvas.height || 600;
|
|
124
|
+
|
|
125
|
+
const layer = this.canvasService.createLayer("overlay", {
|
|
126
|
+
width,
|
|
127
|
+
height,
|
|
128
|
+
left: 0,
|
|
129
|
+
top: 0,
|
|
130
|
+
originX: "left",
|
|
131
|
+
originY: "top",
|
|
132
|
+
selectable: false,
|
|
133
|
+
evented: false,
|
|
134
|
+
subTargetCheck: false,
|
|
135
|
+
interactive: false,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
this.canvasService.canvas.bringObjectToFront(layer);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private async updateFilm() {
|
|
143
|
+
if (!this.canvasService) return;
|
|
144
|
+
const layer = this.canvasService.getLayer("overlay");
|
|
145
|
+
if (!layer) {
|
|
146
|
+
console.warn("[FilmTool] Overlay layer not found");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const { url, opacity } = this;
|
|
151
|
+
|
|
152
|
+
if (!url) {
|
|
153
|
+
const img = this.canvasService.getObject("film-image", "overlay");
|
|
154
|
+
if (img) {
|
|
155
|
+
layer.remove(img);
|
|
156
|
+
this.canvasService.requestRenderAll();
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const width = this.canvasService.canvas.width || 800;
|
|
162
|
+
const height = this.canvasService.canvas.height || 600;
|
|
163
|
+
|
|
164
|
+
let img = this.canvasService.getObject("film-image", "overlay") as Image;
|
|
165
|
+
try {
|
|
166
|
+
if (img) {
|
|
167
|
+
if (img.getSrc() !== url) {
|
|
168
|
+
await img.setSrc(url);
|
|
169
|
+
}
|
|
170
|
+
img.set({ opacity });
|
|
171
|
+
} else {
|
|
172
|
+
img = await Image.fromURL(url, { crossOrigin: "anonymous" });
|
|
173
|
+
img.scaleToWidth(width);
|
|
174
|
+
if (img.getScaledHeight() < height) img.scaleToHeight(height);
|
|
175
|
+
img.set({
|
|
176
|
+
originX: "left",
|
|
177
|
+
originY: "top",
|
|
178
|
+
left: 0,
|
|
179
|
+
top: 0,
|
|
180
|
+
opacity,
|
|
181
|
+
selectable: false,
|
|
182
|
+
evented: false,
|
|
183
|
+
data: { id: "film-image" },
|
|
184
|
+
});
|
|
185
|
+
layer.add(img);
|
|
186
|
+
}
|
|
187
|
+
this.canvasService.requestRenderAll();
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.error("[FilmTool] Failed to load film image", url, error);
|
|
190
|
+
}
|
|
191
|
+
layer.dirty = true;
|
|
192
|
+
this.canvasService.requestRenderAll();
|
|
193
|
+
}
|
|
194
|
+
}
|