@pooder/kit 6.0.1 → 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/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/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/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/SceneLayoutService.js +96 -0
- package/.test-dist/src/services/index.js +1 -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 +116 -0
- package/CHANGELOG.md +6 -0
- package/dist/index.d.mts +390 -367
- package/dist/index.d.ts +390 -367
- package/dist/index.js +5138 -4927
- package/dist/index.mjs +1149 -1977
- 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} +14 -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} +123 -402
- 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/SceneLayoutService.ts +139 -0
- package/src/services/index.ts +1 -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 +146 -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";
|
|
@@ -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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export function cloneWithJson<T>(value: T): T {
|
|
2
|
+
return JSON.parse(JSON.stringify(value)) as T;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface SessionLike<T> {
|
|
6
|
+
committed: T;
|
|
7
|
+
working: T;
|
|
8
|
+
hasWorkingChanges: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function applyCommittedSnapshot<T>(
|
|
12
|
+
session: SessionLike<T>,
|
|
13
|
+
nextCommitted: T,
|
|
14
|
+
options: {
|
|
15
|
+
clone: (value: T) => T;
|
|
16
|
+
toolActive: boolean;
|
|
17
|
+
preserveDirtyWorking?: boolean;
|
|
18
|
+
},
|
|
19
|
+
): void {
|
|
20
|
+
const clone = options.clone;
|
|
21
|
+
session.committed = clone(nextCommitted);
|
|
22
|
+
|
|
23
|
+
const shouldPreserveDirtyWorking =
|
|
24
|
+
options.toolActive &&
|
|
25
|
+
options.preserveDirtyWorking !== false &&
|
|
26
|
+
session.hasWorkingChanges;
|
|
27
|
+
|
|
28
|
+
if (!shouldPreserveDirtyWorking) {
|
|
29
|
+
session.working = clone(session.committed);
|
|
30
|
+
session.hasWorkingChanges = false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function runDeferredConfigUpdate(
|
|
35
|
+
state: any,
|
|
36
|
+
action: () => void,
|
|
37
|
+
cooldownMs = 0,
|
|
38
|
+
): void {
|
|
39
|
+
state.isUpdatingConfig = true;
|
|
40
|
+
action();
|
|
41
|
+
if (cooldownMs <= 0) {
|
|
42
|
+
state.isUpdatingConfig = false;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
state.isUpdatingConfig = false;
|
|
47
|
+
}, cooldownMs);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class WorkingSessionState<T> {
|
|
51
|
+
committed: T;
|
|
52
|
+
working: T;
|
|
53
|
+
hasWorkingChanges = false;
|
|
54
|
+
isUpdatingConfig = false;
|
|
55
|
+
|
|
56
|
+
private readonly clone: (value: T) => T;
|
|
57
|
+
|
|
58
|
+
constructor(initial: T, clone: (value: T) => T) {
|
|
59
|
+
this.clone = clone;
|
|
60
|
+
this.committed = this.clone(initial);
|
|
61
|
+
this.working = this.clone(initial);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
setCommitted(
|
|
65
|
+
next: T,
|
|
66
|
+
options: { toolActive?: boolean; preserveDirtyWorking?: boolean } = {},
|
|
67
|
+
): void {
|
|
68
|
+
this.committed = this.clone(next);
|
|
69
|
+
const shouldPreserveDirtyWorking =
|
|
70
|
+
options.toolActive === true &&
|
|
71
|
+
options.preserveDirtyWorking !== false &&
|
|
72
|
+
this.hasWorkingChanges;
|
|
73
|
+
|
|
74
|
+
if (!shouldPreserveDirtyWorking) {
|
|
75
|
+
this.working = this.clone(this.committed);
|
|
76
|
+
this.hasWorkingChanges = false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setWorking(next: T, options: { markDirty?: boolean } = {}): void {
|
|
81
|
+
this.working = this.clone(next);
|
|
82
|
+
if (options.markDirty !== false) {
|
|
83
|
+
this.hasWorkingChanges = true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
resetWorkingFromCommitted(): void {
|
|
88
|
+
this.working = this.clone(this.committed);
|
|
89
|
+
this.hasWorkingChanges = false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
commitWorkingToCommitted(): void {
|
|
93
|
+
this.committed = this.clone(this.working);
|
|
94
|
+
this.hasWorkingChanges = false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
runConfigUpdate(action: () => void): void {
|
|
98
|
+
this.isUpdatingConfig = true;
|
|
99
|
+
try {
|
|
100
|
+
action();
|
|
101
|
+
} finally {
|
|
102
|
+
this.isUpdatingConfig = false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface Disposable {
|
|
2
|
+
dispose(): void;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
interface EventBusLike {
|
|
6
|
+
on(event: string, handler: (...args: any[]) => void): void;
|
|
7
|
+
off(event: string, handler: (...args: any[]) => void): void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ConfigLike {
|
|
11
|
+
onAnyChange(handler: (event: any) => void): Disposable;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class SubscriptionBag {
|
|
15
|
+
private readonly disposables: Disposable[] = [];
|
|
16
|
+
|
|
17
|
+
add<T extends Disposable | undefined | null>(disposable: T): T {
|
|
18
|
+
if (disposable) {
|
|
19
|
+
this.disposables.push(disposable);
|
|
20
|
+
}
|
|
21
|
+
return disposable;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
on(
|
|
25
|
+
eventBus: EventBusLike,
|
|
26
|
+
event: string,
|
|
27
|
+
handler: (...args: any[]) => void,
|
|
28
|
+
): void {
|
|
29
|
+
eventBus.on(event, handler);
|
|
30
|
+
this.disposables.push({
|
|
31
|
+
dispose: () => eventBus.off(event, handler),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
onConfigChange(configService: ConfigLike, handler: (event: any) => void): void {
|
|
36
|
+
this.add(configService.onAnyChange(handler));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
disposeAll(): void {
|
|
40
|
+
while (this.disposables.length > 0) {
|
|
41
|
+
const disposable = this.disposables.pop();
|
|
42
|
+
disposable?.dispose();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ConfigurationService } from "@pooder/core";
|
|
2
|
+
import type { CanvasService, RenderLayoutRect } from "../../services";
|
|
3
|
+
import { computeSceneLayout, readSizeState } from "./sceneLayoutModel";
|
|
4
|
+
|
|
5
|
+
export interface FrameRect {
|
|
6
|
+
left: number;
|
|
7
|
+
top: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function emptyFrameRect(): FrameRect {
|
|
13
|
+
return { left: 0, top: 0, width: 0, height: 0 };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function resolveCutFrameRect(
|
|
17
|
+
canvasService?: CanvasService,
|
|
18
|
+
configService?: ConfigurationService,
|
|
19
|
+
): FrameRect {
|
|
20
|
+
if (!canvasService || !configService) {
|
|
21
|
+
return emptyFrameRect();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const sizeState = readSizeState(configService);
|
|
25
|
+
const layout = computeSceneLayout(canvasService, sizeState);
|
|
26
|
+
if (!layout) {
|
|
27
|
+
return emptyFrameRect();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return canvasService.toSceneRect({
|
|
31
|
+
left: layout.cutRect.left,
|
|
32
|
+
top: layout.cutRect.top,
|
|
33
|
+
width: layout.cutRect.width,
|
|
34
|
+
height: layout.cutRect.height,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function toLayoutSceneRect(rect: FrameRect): RenderLayoutRect {
|
|
39
|
+
return {
|
|
40
|
+
left: rect.left,
|
|
41
|
+
top: rect.top,
|
|
42
|
+
width: rect.width,
|
|
43
|
+
height: rect.height,
|
|
44
|
+
space: "scene",
|
|
45
|
+
};
|
|
46
|
+
}
|