@pooder/kit 6.0.0 → 6.1.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.
- package/.test-dist/src/extensions/background/BackgroundTool.js +524 -0
- package/.test-dist/src/extensions/background/index.js +17 -0
- package/.test-dist/src/extensions/background.js +1 -1
- package/.test-dist/src/extensions/dieline/DielineTool.js +748 -0
- package/.test-dist/src/extensions/dieline/commands.js +127 -0
- package/.test-dist/src/extensions/dieline/config.js +107 -0
- package/.test-dist/src/extensions/dieline/index.js +21 -0
- package/.test-dist/src/extensions/dieline/model.js +2 -0
- package/.test-dist/src/extensions/dieline/renderer.js +2 -0
- package/.test-dist/src/extensions/dieline.js +4 -0
- package/.test-dist/src/extensions/feature/FeatureTool.js +914 -0
- package/.test-dist/src/extensions/feature/index.js +17 -0
- package/.test-dist/src/extensions/film/FilmTool.js +207 -0
- package/.test-dist/src/extensions/film/index.js +17 -0
- package/.test-dist/src/extensions/image/ImageTool.js +1499 -0
- package/.test-dist/src/extensions/image/commands.js +162 -0
- package/.test-dist/src/extensions/image/config.js +129 -0
- package/.test-dist/src/extensions/image/index.js +21 -0
- package/.test-dist/src/extensions/image/model.js +2 -0
- package/.test-dist/src/extensions/image/renderer.js +5 -0
- package/.test-dist/src/extensions/image.js +182 -7
- package/.test-dist/src/extensions/mirror/MirrorTool.js +104 -0
- package/.test-dist/src/extensions/mirror/index.js +17 -0
- package/.test-dist/src/extensions/ruler/RulerTool.js +442 -0
- package/.test-dist/src/extensions/ruler/index.js +17 -0
- package/.test-dist/src/extensions/sceneLayout.js +2 -93
- package/.test-dist/src/extensions/sceneLayoutModel.js +15 -200
- package/.test-dist/src/extensions/size/SizeTool.js +332 -0
- package/.test-dist/src/extensions/size/index.js +17 -0
- package/.test-dist/src/extensions/white-ink/WhiteInkTool.js +1003 -0
- package/.test-dist/src/extensions/white-ink/commands.js +148 -0
- package/.test-dist/src/extensions/white-ink/config.js +31 -0
- package/.test-dist/src/extensions/white-ink/index.js +21 -0
- package/.test-dist/src/extensions/white-ink/model.js +2 -0
- package/.test-dist/src/extensions/white-ink/renderer.js +5 -0
- package/.test-dist/src/services/CanvasService.js +34 -13
- package/.test-dist/src/services/SceneLayoutService.js +96 -0
- package/.test-dist/src/services/index.js +1 -0
- package/.test-dist/src/services/visibility.js +3 -0
- package/.test-dist/src/shared/constants/layers.js +25 -0
- package/.test-dist/src/shared/imaging/sourceSizeCache.js +82 -0
- package/.test-dist/src/shared/index.js +22 -0
- package/.test-dist/src/shared/runtime/sessionState.js +74 -0
- package/.test-dist/src/shared/runtime/subscriptions.js +30 -0
- package/.test-dist/src/shared/scene/frame.js +34 -0
- package/.test-dist/src/shared/scene/sceneLayoutModel.js +202 -0
- package/.test-dist/tests/run.js +118 -0
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts +403 -366
- package/dist/index.d.ts +403 -366
- package/dist/index.js +5172 -4752
- package/dist/index.mjs +1410 -2027
- package/dist/tracer-PO7CRBYY.mjs +1016 -0
- package/package.json +1 -1
- package/src/extensions/{background.ts → background/BackgroundTool.ts} +33 -50
- package/src/extensions/background/index.ts +1 -0
- package/src/extensions/{dieline.ts → dieline/DielineTool.ts} +18 -218
- package/src/extensions/dieline/commands.ts +109 -0
- package/src/extensions/dieline/config.ts +106 -0
- package/src/extensions/dieline/index.ts +5 -0
- package/src/extensions/dieline/model.ts +1 -0
- package/src/extensions/dieline/renderer.ts +1 -0
- package/src/extensions/{feature.ts → feature/FeatureTool.ts} +27 -21
- package/src/extensions/feature/index.ts +1 -0
- package/src/extensions/{film.ts → film/FilmTool.ts} +36 -48
- package/src/extensions/film/index.ts +1 -0
- package/src/extensions/{image.ts → image/ImageTool.ts} +289 -335
- package/src/extensions/image/commands.ts +176 -0
- package/src/extensions/image/config.ts +128 -0
- package/src/extensions/image/index.ts +5 -0
- package/src/extensions/image/model.ts +1 -0
- package/src/extensions/image/renderer.ts +1 -0
- package/src/extensions/{mirror.ts → mirror/MirrorTool.ts} +1 -1
- package/src/extensions/mirror/index.ts +1 -0
- package/src/extensions/{ruler.ts → ruler/RulerTool.ts} +4 -5
- package/src/extensions/ruler/index.ts +1 -0
- package/src/extensions/sceneLayout.ts +1 -140
- package/src/extensions/sceneLayoutModel.ts +1 -364
- package/src/extensions/{size.ts → size/SizeTool.ts} +7 -6
- package/src/extensions/size/index.ts +1 -0
- package/src/extensions/{white-ink.ts → white-ink/WhiteInkTool.ts} +130 -317
- package/src/extensions/white-ink/commands.ts +157 -0
- package/src/extensions/white-ink/config.ts +30 -0
- package/src/extensions/white-ink/index.ts +5 -0
- package/src/extensions/white-ink/model.ts +1 -0
- package/src/extensions/white-ink/renderer.ts +1 -0
- package/src/services/CanvasService.ts +43 -12
- package/src/services/SceneLayoutService.ts +139 -0
- package/src/services/index.ts +1 -0
- package/src/services/renderSpec.ts +2 -0
- package/src/services/visibility.ts +5 -0
- package/src/shared/constants/layers.ts +23 -0
- package/src/shared/imaging/sourceSizeCache.ts +103 -0
- package/src/shared/index.ts +6 -0
- package/src/shared/runtime/sessionState.ts +105 -0
- package/src/shared/runtime/subscriptions.ts +45 -0
- package/src/shared/scene/frame.ts +46 -0
- package/src/shared/scene/sceneLayoutModel.ts +367 -0
- package/tests/run.ts +151 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { CommandContribution } from "@pooder/core";
|
|
2
|
+
|
|
3
|
+
const WHITE_INK_PREVIEW_IMAGE_VISIBLE_KEY = "whiteInk.previewImageVisible";
|
|
4
|
+
const WHITE_INK_DEFAULT_OPACITY = 0.85;
|
|
5
|
+
|
|
6
|
+
export function createWhiteInkCommands(tool: any): CommandContribution[] {
|
|
7
|
+
return [
|
|
8
|
+
{
|
|
9
|
+
command: "addWhiteInk",
|
|
10
|
+
id: "addWhiteInk",
|
|
11
|
+
title: "Add White Ink",
|
|
12
|
+
handler: async (url: string, options?: Record<string, any>) => {
|
|
13
|
+
return await tool.addWhiteInkEntry(url, options);
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
command: "upsertWhiteInk",
|
|
18
|
+
id: "upsertWhiteInk",
|
|
19
|
+
title: "Upsert White Ink",
|
|
20
|
+
handler: async (url: string, options: Record<string, any> = {}) => {
|
|
21
|
+
return await tool.upsertWhiteInkEntry(url, options);
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
command: "getWhiteInks",
|
|
26
|
+
id: "getWhiteInks",
|
|
27
|
+
title: "Get White Inks",
|
|
28
|
+
handler: () => tool.cloneItems(tool.items),
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
command: "getWhiteInkSettings",
|
|
32
|
+
id: "getWhiteInkSettings",
|
|
33
|
+
title: "Get White Ink Settings",
|
|
34
|
+
handler: () => {
|
|
35
|
+
const first = tool.getEffectiveWhiteInkItem(tool.items);
|
|
36
|
+
const primarySource = tool.getPrimaryImageSource();
|
|
37
|
+
const sourceUrl = tool.resolveSourceUrl(first) || primarySource;
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
id: first?.id || null,
|
|
41
|
+
url: sourceUrl,
|
|
42
|
+
sourceUrl,
|
|
43
|
+
opacity: WHITE_INK_DEFAULT_OPACITY,
|
|
44
|
+
printWithWhiteInk: tool.printWithWhiteInk,
|
|
45
|
+
previewImageVisible: tool.previewImageVisible,
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
command: "setWhiteInkPrintEnabled",
|
|
51
|
+
id: "setWhiteInkPrintEnabled",
|
|
52
|
+
title: "Set White Ink Preview Enabled",
|
|
53
|
+
handler: (enabled: boolean) => {
|
|
54
|
+
tool.printWithWhiteInk = !!enabled;
|
|
55
|
+
const configService = tool.context?.services.get("ConfigurationService");
|
|
56
|
+
configService?.update("whiteInk.printWithWhiteInk", tool.printWithWhiteInk);
|
|
57
|
+
tool.updateWhiteInks();
|
|
58
|
+
return { ok: true };
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
command: "setWhiteInkPreviewImageVisible",
|
|
63
|
+
id: "setWhiteInkPreviewImageVisible",
|
|
64
|
+
title: "Set White Ink Cover Visible",
|
|
65
|
+
handler: (visible: boolean) => {
|
|
66
|
+
tool.previewImageVisible = !!visible;
|
|
67
|
+
const configService = tool.context?.services.get("ConfigurationService");
|
|
68
|
+
configService?.update(
|
|
69
|
+
WHITE_INK_PREVIEW_IMAGE_VISIBLE_KEY,
|
|
70
|
+
tool.previewImageVisible,
|
|
71
|
+
);
|
|
72
|
+
tool.updateWhiteInks();
|
|
73
|
+
return { ok: true };
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
command: "getWorkingWhiteInks",
|
|
78
|
+
id: "getWorkingWhiteInks",
|
|
79
|
+
title: "Get Working White Inks",
|
|
80
|
+
handler: () => tool.cloneItems(tool.workingItems),
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
command: "setWorkingWhiteInk",
|
|
84
|
+
id: "setWorkingWhiteInk",
|
|
85
|
+
title: "Set Working White Ink",
|
|
86
|
+
handler: (id: string, updates: Record<string, any>) => {
|
|
87
|
+
tool.updateWhiteInkInWorking(id, updates);
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
command: "updateWhiteInk",
|
|
92
|
+
id: "updateWhiteInk",
|
|
93
|
+
title: "Update White Ink",
|
|
94
|
+
handler: async (
|
|
95
|
+
id: string,
|
|
96
|
+
updates: Record<string, any>,
|
|
97
|
+
options: Record<string, any> = {},
|
|
98
|
+
) => {
|
|
99
|
+
await tool.updateWhiteInkItem(id, updates, options);
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
command: "removeWhiteInk",
|
|
104
|
+
id: "removeWhiteInk",
|
|
105
|
+
title: "Remove White Ink",
|
|
106
|
+
handler: (id: string) => {
|
|
107
|
+
tool.removeWhiteInk(id);
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
command: "clearWhiteInks",
|
|
112
|
+
id: "clearWhiteInks",
|
|
113
|
+
title: "Clear White Inks",
|
|
114
|
+
handler: () => {
|
|
115
|
+
tool.clearWhiteInks();
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
command: "resetWorkingWhiteInks",
|
|
120
|
+
id: "resetWorkingWhiteInks",
|
|
121
|
+
title: "Reset Working White Inks",
|
|
122
|
+
handler: () => {
|
|
123
|
+
tool.workingItems = tool.cloneItems(tool.items);
|
|
124
|
+
tool.hasWorkingChanges = false;
|
|
125
|
+
tool.updateWhiteInks();
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
command: "completeWhiteInks",
|
|
130
|
+
id: "completeWhiteInks",
|
|
131
|
+
title: "Complete White Inks",
|
|
132
|
+
handler: async () => {
|
|
133
|
+
return await tool.completeWhiteInks();
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
command: "setWhiteInkImage",
|
|
138
|
+
id: "setWhiteInkImage",
|
|
139
|
+
title: "Set White Ink Image",
|
|
140
|
+
handler: async (url: string) => {
|
|
141
|
+
if (!url) {
|
|
142
|
+
tool.clearWhiteInks();
|
|
143
|
+
return { ok: true };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const targetId = tool.resolveReplaceTargetId(null);
|
|
147
|
+
const upsertResult = await tool.upsertWhiteInkEntry(url, {
|
|
148
|
+
id: targetId || undefined,
|
|
149
|
+
mode: targetId ? "replace" : "add",
|
|
150
|
+
createIfMissing: true,
|
|
151
|
+
addOptions: {},
|
|
152
|
+
});
|
|
153
|
+
return { ok: true, id: upsertResult.id };
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
];
|
|
157
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ConfigurationContribution } from "@pooder/core";
|
|
2
|
+
|
|
3
|
+
export function createWhiteInkConfigurations(): ConfigurationContribution[] {
|
|
4
|
+
return [
|
|
5
|
+
{
|
|
6
|
+
id: "whiteInk.items",
|
|
7
|
+
type: "array",
|
|
8
|
+
label: "White Ink Images",
|
|
9
|
+
default: [],
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: "whiteInk.printWithWhiteInk",
|
|
13
|
+
type: "boolean",
|
|
14
|
+
label: "Preview White Ink",
|
|
15
|
+
default: true,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "whiteInk.previewImageVisible",
|
|
19
|
+
type: "boolean",
|
|
20
|
+
label: "Show Cover During White Ink Preview",
|
|
21
|
+
default: true,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "whiteInk.debug",
|
|
25
|
+
type: "boolean",
|
|
26
|
+
label: "White Ink Debug Log",
|
|
27
|
+
default: false,
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { WhiteInkItem } from "./WhiteInkTool";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getCoverScale as computeWhiteInkCoverScale } from "../../shared/imaging/sourceSizeCache";
|
|
@@ -60,6 +60,7 @@ interface ResolvedRenderPassSpec {
|
|
|
60
60
|
interface ResolvedClipPathEffectSpec {
|
|
61
61
|
type: "clipPath";
|
|
62
62
|
key: string;
|
|
63
|
+
visibility?: RenderPassSpec["visibility"];
|
|
63
64
|
source: RenderObjectSpec;
|
|
64
65
|
targetPassIds: string[];
|
|
65
66
|
}
|
|
@@ -89,6 +90,7 @@ export default class CanvasService implements Service {
|
|
|
89
90
|
|
|
90
91
|
private managedProducerPassIds: Set<string> = new Set();
|
|
91
92
|
private managedPassMetas: Map<string, ManagedPassMeta> = new Map();
|
|
93
|
+
private managedPassEffects: ResolvedClipPathEffectSpec[] = [];
|
|
92
94
|
|
|
93
95
|
private canvasForwardersBound = false;
|
|
94
96
|
private readonly forwardSelectionCreated = (e: any) => {
|
|
@@ -112,9 +114,11 @@ export default class CanvasService implements Service {
|
|
|
112
114
|
|
|
113
115
|
private readonly onToolActivated = () => {
|
|
114
116
|
this.applyManagedPassVisibility();
|
|
117
|
+
void this.applyManagedPassEffects(undefined, { render: true });
|
|
115
118
|
};
|
|
116
119
|
private readonly onToolSessionChanged = () => {
|
|
117
120
|
this.applyManagedPassVisibility();
|
|
121
|
+
void this.applyManagedPassEffects(undefined, { render: true });
|
|
118
122
|
};
|
|
119
123
|
private readonly onCanvasObjectChanged = () => {
|
|
120
124
|
if (this.producerApplyInProgress) return;
|
|
@@ -190,6 +194,7 @@ export default class CanvasService implements Service {
|
|
|
190
194
|
this.renderProducers.clear();
|
|
191
195
|
this.managedProducerPassIds.clear();
|
|
192
196
|
this.managedPassMetas.clear();
|
|
197
|
+
this.managedPassEffects = [];
|
|
193
198
|
this.context = undefined;
|
|
194
199
|
this.workbenchService = undefined;
|
|
195
200
|
this.toolSessionService = undefined;
|
|
@@ -332,6 +337,7 @@ export default class CanvasService implements Service {
|
|
|
332
337
|
return {
|
|
333
338
|
type: "clipPath",
|
|
334
339
|
key,
|
|
340
|
+
visibility: effect.visibility,
|
|
335
341
|
source: {
|
|
336
342
|
...source,
|
|
337
343
|
id: sourceId,
|
|
@@ -466,23 +472,35 @@ export default class CanvasService implements Service {
|
|
|
466
472
|
return state;
|
|
467
473
|
}
|
|
468
474
|
|
|
475
|
+
private isSessionActive(toolId: string): boolean {
|
|
476
|
+
if (!this.toolSessionService) return false;
|
|
477
|
+
return this.toolSessionService.getState(toolId).status === "active";
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
private hasAnyActiveSession(): boolean {
|
|
481
|
+
return this.toolSessionService?.hasAnyActiveSession() ?? false;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
private buildVisibilityEvalContext(
|
|
485
|
+
layers: Map<string, VisibilityLayerState>,
|
|
486
|
+
) {
|
|
487
|
+
return {
|
|
488
|
+
activeToolId: this.workbenchService?.activeToolId ?? null,
|
|
489
|
+
isSessionActive: (toolId: string) => this.isSessionActive(toolId),
|
|
490
|
+
hasAnyActiveSession: () => this.hasAnyActiveSession(),
|
|
491
|
+
layers,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
469
495
|
private applyManagedPassVisibility(options: { render?: boolean } = {}): boolean {
|
|
470
496
|
if (!this.managedPassMetas.size) return false;
|
|
471
497
|
const layers = this.getPassRuntimeState();
|
|
472
|
-
const
|
|
473
|
-
const isSessionActive = (toolId: string) => {
|
|
474
|
-
if (!this.toolSessionService) return false;
|
|
475
|
-
return this.toolSessionService.getState(toolId).status === "active";
|
|
476
|
-
};
|
|
498
|
+
const context = this.buildVisibilityEvalContext(layers);
|
|
477
499
|
|
|
478
500
|
let changed = false;
|
|
479
501
|
|
|
480
502
|
this.managedPassMetas.forEach((meta) => {
|
|
481
|
-
const visible = evaluateVisibilityExpr(meta.visibility,
|
|
482
|
-
activeToolId,
|
|
483
|
-
isSessionActive,
|
|
484
|
-
layers,
|
|
485
|
-
});
|
|
503
|
+
const visible = evaluateVisibilityExpr(meta.visibility, context);
|
|
486
504
|
changed = this.setPassVisibility(meta.id, visible) || changed;
|
|
487
505
|
});
|
|
488
506
|
|
|
@@ -560,9 +578,10 @@ export default class CanvasService implements Service {
|
|
|
560
578
|
|
|
561
579
|
this.managedProducerPassIds = nextPassIds;
|
|
562
580
|
this.managedPassMetas = nextManagedPassMetas;
|
|
581
|
+
this.managedPassEffects = nextEffects;
|
|
563
582
|
|
|
564
583
|
this.syncManagedPassStacking(Array.from(nextManagedPassMetas.values()));
|
|
565
|
-
await this.applyManagedPassEffects(nextEffects);
|
|
584
|
+
await this.applyManagedPassEffects(nextEffects, { render: false });
|
|
566
585
|
this.applyManagedPassVisibility({ render: false });
|
|
567
586
|
} finally {
|
|
568
587
|
this.producerApplyInProgress = false;
|
|
@@ -571,11 +590,19 @@ export default class CanvasService implements Service {
|
|
|
571
590
|
this.requestRenderAll();
|
|
572
591
|
}
|
|
573
592
|
|
|
574
|
-
private async applyManagedPassEffects(
|
|
593
|
+
private async applyManagedPassEffects(
|
|
594
|
+
effects: ResolvedClipPathEffectSpec[] = this.managedPassEffects,
|
|
595
|
+
options: { render?: boolean } = {},
|
|
596
|
+
) {
|
|
575
597
|
const effectTargetMap = new Map<FabricObject, ResolvedClipPathEffectSpec>();
|
|
598
|
+
const layers = this.getPassRuntimeState();
|
|
599
|
+
const visibilityContext = this.buildVisibilityEvalContext(layers);
|
|
576
600
|
|
|
577
601
|
for (const effect of effects) {
|
|
578
602
|
if (effect.type !== "clipPath") continue;
|
|
603
|
+
if (!evaluateVisibilityExpr(effect.visibility, visibilityContext)) {
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
579
606
|
effect.targetPassIds.forEach((targetPassId) => {
|
|
580
607
|
this.getPassCanvasObjects(targetPassId).forEach((obj) => {
|
|
581
608
|
effectTargetMap.set(obj, effect);
|
|
@@ -613,6 +640,10 @@ export default class CanvasService implements Service {
|
|
|
613
640
|
targetEffect.key,
|
|
614
641
|
);
|
|
615
642
|
}
|
|
643
|
+
|
|
644
|
+
if (options.render !== false) {
|
|
645
|
+
this.requestRenderAll();
|
|
646
|
+
}
|
|
616
647
|
}
|
|
617
648
|
|
|
618
649
|
getObject(id: string, passId?: string): FabricObject | undefined {
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {
|
|
2
|
+
COMMAND_SERVICE,
|
|
3
|
+
CONFIGURATION_SERVICE,
|
|
4
|
+
CommandService,
|
|
5
|
+
ConfigurationService,
|
|
6
|
+
Service,
|
|
7
|
+
ServiceContext,
|
|
8
|
+
} from "@pooder/core";
|
|
9
|
+
import CanvasService from "./CanvasService";
|
|
10
|
+
import {
|
|
11
|
+
buildSceneGeometry,
|
|
12
|
+
computeSceneLayout,
|
|
13
|
+
readSizeState,
|
|
14
|
+
type SceneGeometrySnapshot,
|
|
15
|
+
type SceneLayoutSnapshot,
|
|
16
|
+
} from "../shared/scene/sceneLayoutModel";
|
|
17
|
+
import { SubscriptionBag } from "../shared/runtime/subscriptions";
|
|
18
|
+
|
|
19
|
+
interface ConfigChangeEvent {
|
|
20
|
+
key: string;
|
|
21
|
+
value: unknown;
|
|
22
|
+
oldValue: unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const CONFIG_WATCH_PREFIXES = ["size.", "dieline."] as const;
|
|
26
|
+
const CANVAS_SERVICE_ID = "CanvasService";
|
|
27
|
+
const GET_SCENE_LAYOUT_COMMAND = "getSceneLayout";
|
|
28
|
+
const GET_SCENE_GEOMETRY_COMMAND = "getSceneGeometry";
|
|
29
|
+
|
|
30
|
+
export class SceneLayoutService implements Service {
|
|
31
|
+
private context?: ServiceContext;
|
|
32
|
+
private canvasService?: CanvasService;
|
|
33
|
+
private configService?: ConfigurationService;
|
|
34
|
+
private lastLayout: SceneLayoutSnapshot | null = null;
|
|
35
|
+
private lastGeometry: SceneGeometrySnapshot | null = null;
|
|
36
|
+
private readonly subscriptions = new SubscriptionBag();
|
|
37
|
+
private commandDisposables: Array<{ dispose(): void }> = [];
|
|
38
|
+
|
|
39
|
+
init(context: ServiceContext) {
|
|
40
|
+
if (this.context) {
|
|
41
|
+
this.dispose(this.context);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const canvasService =
|
|
45
|
+
context.get<CanvasService>(CANVAS_SERVICE_ID);
|
|
46
|
+
const configService =
|
|
47
|
+
context.get<ConfigurationService>(CONFIGURATION_SERVICE);
|
|
48
|
+
const commandService = context.get<CommandService>(COMMAND_SERVICE);
|
|
49
|
+
|
|
50
|
+
if (!canvasService || !configService || !commandService) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
"[SceneLayoutService] CanvasService, ConfigurationService and CommandService are required.",
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.context = context;
|
|
57
|
+
this.canvasService = canvasService;
|
|
58
|
+
this.configService = configService;
|
|
59
|
+
|
|
60
|
+
this.commandDisposables.push(
|
|
61
|
+
commandService.registerCommand(GET_SCENE_LAYOUT_COMMAND, () =>
|
|
62
|
+
this.getLayout(),
|
|
63
|
+
),
|
|
64
|
+
commandService.registerCommand(GET_SCENE_GEOMETRY_COMMAND, () =>
|
|
65
|
+
this.getGeometry(),
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
this.subscriptions.disposeAll();
|
|
70
|
+
this.subscriptions.onConfigChange(configService, this.onConfigChanged);
|
|
71
|
+
this.subscriptions.on(context.eventBus, "canvas:resized", this.onCanvasResized);
|
|
72
|
+
this.refresh();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
dispose(context: ServiceContext) {
|
|
76
|
+
this.subscriptions.disposeAll();
|
|
77
|
+
this.commandDisposables.forEach((item) => item.dispose());
|
|
78
|
+
this.commandDisposables = [];
|
|
79
|
+
this.context = undefined;
|
|
80
|
+
this.canvasService = undefined;
|
|
81
|
+
this.configService = undefined;
|
|
82
|
+
this.lastLayout = null;
|
|
83
|
+
this.lastGeometry = null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private onCanvasResized = () => {
|
|
87
|
+
this.refresh();
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
private onConfigChanged = (e: ConfigChangeEvent) => {
|
|
91
|
+
if (CONFIG_WATCH_PREFIXES.some((prefix) => e.key.startsWith(prefix))) {
|
|
92
|
+
this.refresh();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
private refresh() {
|
|
97
|
+
const layout = this.getLayout(true);
|
|
98
|
+
if (!layout) {
|
|
99
|
+
this.lastGeometry = null;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.context?.eventBus.emit("scene:layout:change", layout);
|
|
104
|
+
|
|
105
|
+
const geometry = this.getGeometry(true);
|
|
106
|
+
if (geometry) {
|
|
107
|
+
this.context?.eventBus.emit("scene:geometry:change", geometry);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
getLayout(forceRefresh = false): SceneLayoutSnapshot | null {
|
|
112
|
+
if (!this.canvasService || !this.configService) return null;
|
|
113
|
+
if (!forceRefresh && this.lastLayout) return this.lastLayout;
|
|
114
|
+
|
|
115
|
+
const state = readSizeState(this.configService);
|
|
116
|
+
const layout = computeSceneLayout(this.canvasService, state);
|
|
117
|
+
if (!layout) {
|
|
118
|
+
this.lastLayout = null;
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.lastLayout = layout;
|
|
123
|
+
return layout;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getGeometry(forceRefresh = false): SceneGeometrySnapshot | null {
|
|
127
|
+
if (!this.configService) return null;
|
|
128
|
+
const layout = this.getLayout(forceRefresh);
|
|
129
|
+
if (!layout) {
|
|
130
|
+
this.lastGeometry = null;
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
if (!forceRefresh && this.lastGeometry) return this.lastGeometry;
|
|
134
|
+
|
|
135
|
+
const geometry = buildSceneGeometry(this.configService, layout);
|
|
136
|
+
this.lastGeometry = geometry;
|
|
137
|
+
return geometry;
|
|
138
|
+
}
|
|
139
|
+
}
|
package/src/services/index.ts
CHANGED
|
@@ -52,6 +52,7 @@ export type VisibilityExpr =
|
|
|
52
52
|
| { op: "const"; value: boolean }
|
|
53
53
|
| { op: "activeToolIn"; ids: string[] }
|
|
54
54
|
| { op: "sessionActive"; toolId: string }
|
|
55
|
+
| { op: "anySessionActive" }
|
|
55
56
|
| { op: "layerExists"; layerId: string }
|
|
56
57
|
| {
|
|
57
58
|
op: "layerObjectCount";
|
|
@@ -66,6 +67,7 @@ export type VisibilityExpr =
|
|
|
66
67
|
export interface RenderClipPathEffectSpec {
|
|
67
68
|
type: "clipPath";
|
|
68
69
|
id?: string;
|
|
70
|
+
visibility?: VisibilityExpr;
|
|
69
71
|
source: RenderObjectSpec;
|
|
70
72
|
targetPassIds: string[];
|
|
71
73
|
}
|
|
@@ -8,6 +8,7 @@ export interface VisibilityLayerState {
|
|
|
8
8
|
export interface VisibilityEvalContext {
|
|
9
9
|
activeToolId?: string | null;
|
|
10
10
|
isSessionActive?: (toolId: string) => boolean;
|
|
11
|
+
hasAnyActiveSession?: () => boolean;
|
|
11
12
|
layers: Map<string, VisibilityLayerState>;
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -51,6 +52,10 @@ export function evaluateVisibilityExpr(
|
|
|
51
52
|
return context.isSessionActive ? context.isSessionActive(toolId) : false;
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
if (expr.op === "anySessionActive") {
|
|
56
|
+
return context.hasAnyActiveSession ? context.hasAnyActiveSession() : false;
|
|
57
|
+
}
|
|
58
|
+
|
|
54
59
|
if (expr.op === "layerExists") {
|
|
55
60
|
return layerState(context, expr.layerId).exists === true;
|
|
56
61
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const BACKGROUND_LAYER_ID = "background";
|
|
2
|
+
export const IMAGE_OBJECT_LAYER_ID = "image.user";
|
|
3
|
+
export const IMAGE_OVERLAY_LAYER_ID = "image-overlay";
|
|
4
|
+
export const WHITE_INK_OBJECT_LAYER_ID = "white-ink.user";
|
|
5
|
+
export const WHITE_INK_COVER_LAYER_ID = "white-ink.cover";
|
|
6
|
+
export const WHITE_INK_OVERLAY_LAYER_ID = "white-ink.overlay";
|
|
7
|
+
export const DIELINE_LAYER_ID = "dieline-overlay";
|
|
8
|
+
export const FEATURE_OVERLAY_LAYER_ID = "feature-overlay";
|
|
9
|
+
export const RULER_LAYER_ID = "ruler-overlay";
|
|
10
|
+
export const FILM_LAYER_ID = "overlay";
|
|
11
|
+
|
|
12
|
+
export const LAYER_IDS = {
|
|
13
|
+
background: BACKGROUND_LAYER_ID,
|
|
14
|
+
imageObject: IMAGE_OBJECT_LAYER_ID,
|
|
15
|
+
imageOverlay: IMAGE_OVERLAY_LAYER_ID,
|
|
16
|
+
whiteInkObject: WHITE_INK_OBJECT_LAYER_ID,
|
|
17
|
+
whiteInkCover: WHITE_INK_COVER_LAYER_ID,
|
|
18
|
+
whiteInkOverlay: WHITE_INK_OVERLAY_LAYER_ID,
|
|
19
|
+
dieline: DIELINE_LAYER_ID,
|
|
20
|
+
featureOverlay: FEATURE_OVERLAY_LAYER_ID,
|
|
21
|
+
rulerOverlay: RULER_LAYER_ID,
|
|
22
|
+
filmOverlay: FILM_LAYER_ID,
|
|
23
|
+
} as const;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export interface SourceSize {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface RectLike {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SourceSizeCache {
|
|
12
|
+
ensureImageSize: (src: string) => Promise<SourceSize | null>;
|
|
13
|
+
rememberSourceSize: (src: string, size: Partial<SourceSize>) => SourceSize | null;
|
|
14
|
+
getSourceSize: (src: string) => SourceSize | null;
|
|
15
|
+
deleteSourceSize: (src: string) => void;
|
|
16
|
+
clear: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function normalizeSourceSize(size: Partial<SourceSize>): SourceSize | null {
|
|
20
|
+
const width = Number(size.width || 0);
|
|
21
|
+
const height = Number(size.height || 0);
|
|
22
|
+
if (!Number.isFinite(width) || !Number.isFinite(height)) return null;
|
|
23
|
+
if (width <= 0 || height <= 0) return null;
|
|
24
|
+
return { width, height };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getCoverScale(frame: RectLike, source: RectLike): number {
|
|
28
|
+
const frameWidth = Math.max(1, Number(frame.width || 0));
|
|
29
|
+
const frameHeight = Math.max(1, Number(frame.height || 0));
|
|
30
|
+
const sourceWidth = Math.max(1, Number(source.width || 0));
|
|
31
|
+
const sourceHeight = Math.max(1, Number(source.height || 0));
|
|
32
|
+
return Math.max(frameWidth / sourceWidth, frameHeight / sourceHeight);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createSourceSizeCache(
|
|
36
|
+
loadSize: (src: string) => Promise<SourceSize | null>,
|
|
37
|
+
): SourceSizeCache {
|
|
38
|
+
const sizesBySrc = new Map<string, SourceSize>();
|
|
39
|
+
const pendingBySrc = new Map<string, Promise<SourceSize | null>>();
|
|
40
|
+
|
|
41
|
+
const rememberSourceSize = (
|
|
42
|
+
src: string,
|
|
43
|
+
size: Partial<SourceSize>,
|
|
44
|
+
): SourceSize | null => {
|
|
45
|
+
const normalized = normalizeSourceSize(size);
|
|
46
|
+
if (!src || !normalized) return null;
|
|
47
|
+
sizesBySrc.set(src, normalized);
|
|
48
|
+
return normalized;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const getSourceSize = (src: string): SourceSize | null => {
|
|
52
|
+
if (!src) return null;
|
|
53
|
+
const cached = sizesBySrc.get(src);
|
|
54
|
+
if (!cached) return null;
|
|
55
|
+
return { width: cached.width, height: cached.height };
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const ensureImageSize = async (src: string): Promise<SourceSize | null> => {
|
|
59
|
+
if (!src) return null;
|
|
60
|
+
|
|
61
|
+
const cached = sizesBySrc.get(src);
|
|
62
|
+
if (cached) return { width: cached.width, height: cached.height };
|
|
63
|
+
|
|
64
|
+
const pending = pendingBySrc.get(src);
|
|
65
|
+
if (pending) {
|
|
66
|
+
return pending;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const task = loadSize(src);
|
|
70
|
+
pendingBySrc.set(src, task);
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const size = await task;
|
|
74
|
+
if (size) {
|
|
75
|
+
rememberSourceSize(src, size);
|
|
76
|
+
}
|
|
77
|
+
return size;
|
|
78
|
+
} finally {
|
|
79
|
+
if (pendingBySrc.get(src) === task) {
|
|
80
|
+
pendingBySrc.delete(src);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const deleteSourceSize = (src: string) => {
|
|
86
|
+
if (!src) return;
|
|
87
|
+
sizesBySrc.delete(src);
|
|
88
|
+
pendingBySrc.delete(src);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const clear = () => {
|
|
92
|
+
sizesBySrc.clear();
|
|
93
|
+
pendingBySrc.clear();
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
ensureImageSize,
|
|
98
|
+
rememberSourceSize,
|
|
99
|
+
getSourceSize,
|
|
100
|
+
deleteSourceSize,
|
|
101
|
+
clear,
|
|
102
|
+
};
|
|
103
|
+
}
|