@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
|
@@ -2,27 +2,48 @@ import {
|
|
|
2
2
|
Extension,
|
|
3
3
|
ExtensionContext,
|
|
4
4
|
ContributionPointIds,
|
|
5
|
-
CommandContribution,
|
|
6
|
-
ConfigurationContribution,
|
|
7
5
|
ConfigurationService,
|
|
8
6
|
ToolSessionService,
|
|
9
7
|
WorkbenchService,
|
|
10
8
|
} from "@pooder/core";
|
|
11
9
|
import {
|
|
12
10
|
Canvas as FabricCanvas,
|
|
11
|
+
Control,
|
|
13
12
|
Image as FabricImage,
|
|
14
13
|
Pattern,
|
|
15
14
|
Point,
|
|
15
|
+
controlsUtils,
|
|
16
16
|
} from "fabric";
|
|
17
|
-
import { CanvasService, RenderLayoutRect, RenderObjectSpec } from "
|
|
18
|
-
import { isDielineShape, normalizeShapeStyle } from "
|
|
19
|
-
import type { DielineShape, DielineShapeStyle } from "
|
|
20
|
-
import { generateDielinePath, getPathBounds } from "
|
|
17
|
+
import { CanvasService, RenderLayoutRect, RenderObjectSpec } from "../../services";
|
|
18
|
+
import { isDielineShape, normalizeShapeStyle } from "../dielineShape";
|
|
19
|
+
import type { DielineShape, DielineShapeStyle } from "../dielineShape";
|
|
20
|
+
import { generateDielinePath, getPathBounds } from "../geometry";
|
|
21
21
|
import {
|
|
22
22
|
buildSceneGeometry,
|
|
23
23
|
computeSceneLayout,
|
|
24
24
|
readSizeState,
|
|
25
|
-
} from "
|
|
25
|
+
} from "../../shared/scene/sceneLayoutModel";
|
|
26
|
+
import {
|
|
27
|
+
type FrameRect,
|
|
28
|
+
resolveCutFrameRect,
|
|
29
|
+
toLayoutSceneRect as toSceneLayoutRect,
|
|
30
|
+
} from "../../shared/scene/frame";
|
|
31
|
+
import {
|
|
32
|
+
createSourceSizeCache,
|
|
33
|
+
getCoverScale as getCoverScaleFromRect,
|
|
34
|
+
type SourceSize,
|
|
35
|
+
} from "../../shared/imaging/sourceSizeCache";
|
|
36
|
+
import { SubscriptionBag } from "../../shared/runtime/subscriptions";
|
|
37
|
+
import {
|
|
38
|
+
applyCommittedSnapshot,
|
|
39
|
+
runDeferredConfigUpdate,
|
|
40
|
+
} from "../../shared/runtime/sessionState";
|
|
41
|
+
import {
|
|
42
|
+
IMAGE_OBJECT_LAYER_ID,
|
|
43
|
+
IMAGE_OVERLAY_LAYER_ID,
|
|
44
|
+
} from "../../shared/constants/layers";
|
|
45
|
+
import { createImageCommands } from "./commands";
|
|
46
|
+
import { createImageConfigurations } from "./config";
|
|
26
47
|
|
|
27
48
|
export interface ImageItem {
|
|
28
49
|
id: string;
|
|
@@ -36,18 +57,6 @@ export interface ImageItem {
|
|
|
36
57
|
committedUrl?: string;
|
|
37
58
|
}
|
|
38
59
|
|
|
39
|
-
interface FrameRect {
|
|
40
|
-
left: number;
|
|
41
|
-
top: number;
|
|
42
|
-
width: number;
|
|
43
|
-
height: number;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface SourceSize {
|
|
47
|
-
width: number;
|
|
48
|
-
height: number;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
60
|
interface RenderImageState {
|
|
52
61
|
src: string;
|
|
53
62
|
left: number;
|
|
@@ -66,6 +75,18 @@ interface FrameVisualConfig {
|
|
|
66
75
|
outerBackground: string;
|
|
67
76
|
}
|
|
68
77
|
|
|
78
|
+
interface ImageControlVisualConfig {
|
|
79
|
+
cornerSize: number;
|
|
80
|
+
touchCornerSize: number;
|
|
81
|
+
cornerStyle: "rect" | "circle";
|
|
82
|
+
cornerColor: string;
|
|
83
|
+
cornerStrokeColor: string;
|
|
84
|
+
transparentCorners: boolean;
|
|
85
|
+
borderColor: string;
|
|
86
|
+
borderScaleFactor: number;
|
|
87
|
+
padding: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
69
90
|
type ShapeOverlayShape = Exclude<DielineShape, "custom">;
|
|
70
91
|
|
|
71
92
|
interface SceneGeometryLike {
|
|
@@ -111,8 +132,45 @@ interface ExportUserCroppedImageResult {
|
|
|
111
132
|
imageIds: string[];
|
|
112
133
|
}
|
|
113
134
|
|
|
114
|
-
|
|
115
|
-
|
|
135
|
+
type ImageControlCapability = "rotate" | "scale" | "flipX" | "flipY";
|
|
136
|
+
|
|
137
|
+
interface ImageControlDescriptor {
|
|
138
|
+
key: string;
|
|
139
|
+
capability: ImageControlCapability;
|
|
140
|
+
create: () => Control;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const IMAGE_DEFAULT_CONTROL_CAPABILITIES: ImageControlCapability[] = [
|
|
144
|
+
"rotate",
|
|
145
|
+
"scale",
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
const IMAGE_CONTROL_DESCRIPTORS: ImageControlDescriptor[] = [
|
|
149
|
+
{
|
|
150
|
+
key: "tl",
|
|
151
|
+
capability: "rotate",
|
|
152
|
+
create: () =>
|
|
153
|
+
new Control({
|
|
154
|
+
x: -0.5,
|
|
155
|
+
y: -0.5,
|
|
156
|
+
actionName: "rotate",
|
|
157
|
+
actionHandler: controlsUtils.rotationWithSnapping,
|
|
158
|
+
cursorStyleHandler: controlsUtils.rotationStyleHandler,
|
|
159
|
+
}),
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
key: "br",
|
|
163
|
+
capability: "scale",
|
|
164
|
+
create: () =>
|
|
165
|
+
new Control({
|
|
166
|
+
x: 0.5,
|
|
167
|
+
y: 0.5,
|
|
168
|
+
actionName: "scale",
|
|
169
|
+
actionHandler: controlsUtils.scalingEqually,
|
|
170
|
+
cursorStyleHandler: controlsUtils.scaleCursorStyleHandler,
|
|
171
|
+
}),
|
|
172
|
+
},
|
|
173
|
+
];
|
|
116
174
|
|
|
117
175
|
export class ImageTool implements Extension {
|
|
118
176
|
id = "pooder.kit.image";
|
|
@@ -125,7 +183,9 @@ export class ImageTool implements Extension {
|
|
|
125
183
|
private workingItems: ImageItem[] = [];
|
|
126
184
|
private hasWorkingChanges = false;
|
|
127
185
|
private loadResolvers: Map<string, () => void> = new Map();
|
|
128
|
-
private
|
|
186
|
+
private sourceSizeCache = createSourceSizeCache((src) =>
|
|
187
|
+
this.loadImageSize(src),
|
|
188
|
+
);
|
|
129
189
|
private canvasService?: CanvasService;
|
|
130
190
|
private context?: ExtensionContext;
|
|
131
191
|
private isUpdatingConfig = false;
|
|
@@ -140,8 +200,12 @@ export class ImageTool implements Extension {
|
|
|
140
200
|
private imageSpecs: RenderObjectSpec[] = [];
|
|
141
201
|
private overlaySpecs: RenderObjectSpec[] = [];
|
|
142
202
|
private renderProducerDisposable?: { dispose: () => void };
|
|
203
|
+
private readonly subscriptions = new SubscriptionBag();
|
|
204
|
+
private imageControlsByCapabilityKey: Map<string, Record<string, Control>> =
|
|
205
|
+
new Map();
|
|
143
206
|
|
|
144
207
|
activate(context: ExtensionContext) {
|
|
208
|
+
this.subscriptions.disposeAll();
|
|
145
209
|
this.context = context;
|
|
146
210
|
this.canvasService = context.services.get<CanvasService>("CanvasService");
|
|
147
211
|
if (!this.canvasService) {
|
|
@@ -184,41 +248,63 @@ export class ImageTool implements Extension {
|
|
|
184
248
|
{ priority: 300 },
|
|
185
249
|
);
|
|
186
250
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
251
|
+
this.subscriptions.on(context.eventBus, "tool:activated", this.onToolActivated);
|
|
252
|
+
this.subscriptions.on(context.eventBus, "object:modified", this.onObjectModified);
|
|
253
|
+
this.subscriptions.on(
|
|
254
|
+
context.eventBus,
|
|
255
|
+
"selection:created",
|
|
256
|
+
this.onSelectionChanged,
|
|
257
|
+
);
|
|
258
|
+
this.subscriptions.on(
|
|
259
|
+
context.eventBus,
|
|
260
|
+
"selection:updated",
|
|
261
|
+
this.onSelectionChanged,
|
|
262
|
+
);
|
|
263
|
+
this.subscriptions.on(
|
|
264
|
+
context.eventBus,
|
|
265
|
+
"selection:cleared",
|
|
266
|
+
this.onSelectionCleared,
|
|
267
|
+
);
|
|
268
|
+
this.subscriptions.on(
|
|
269
|
+
context.eventBus,
|
|
270
|
+
"scene:layout:change",
|
|
271
|
+
this.onSceneLayoutChanged,
|
|
272
|
+
);
|
|
273
|
+
this.subscriptions.on(
|
|
274
|
+
context.eventBus,
|
|
275
|
+
"scene:geometry:change",
|
|
276
|
+
this.onSceneGeometryChanged,
|
|
277
|
+
);
|
|
194
278
|
|
|
195
279
|
const configService = context.services.get<ConfigurationService>(
|
|
196
280
|
"ConfigurationService",
|
|
197
281
|
);
|
|
198
282
|
if (configService) {
|
|
199
|
-
this.items
|
|
200
|
-
configService.get("image.items", []) || [],
|
|
201
|
-
);
|
|
202
|
-
this.workingItems = this.cloneItems(this.items);
|
|
203
|
-
this.hasWorkingChanges = false;
|
|
283
|
+
this.applyCommittedItems(configService.get("image.items", []) || []);
|
|
204
284
|
|
|
205
|
-
|
|
206
|
-
|
|
285
|
+
this.subscriptions.onConfigChange(
|
|
286
|
+
configService,
|
|
287
|
+
(e: { key: string; value: any }) => {
|
|
288
|
+
if (this.isUpdatingConfig) return;
|
|
207
289
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
this.hasWorkingChanges = false;
|
|
290
|
+
if (e.key === "image.items") {
|
|
291
|
+
this.applyCommittedItems(e.value || []);
|
|
292
|
+
this.updateImages();
|
|
293
|
+
return;
|
|
213
294
|
}
|
|
214
|
-
this.updateImages();
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
295
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
296
|
+
if (
|
|
297
|
+
e.key.startsWith("size.") ||
|
|
298
|
+
e.key.startsWith("image.frame.") ||
|
|
299
|
+
e.key.startsWith("image.control.")
|
|
300
|
+
) {
|
|
301
|
+
if (e.key.startsWith("image.control.")) {
|
|
302
|
+
this.imageControlsByCapabilityKey.clear();
|
|
303
|
+
}
|
|
304
|
+
this.updateImages();
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
);
|
|
222
308
|
}
|
|
223
309
|
|
|
224
310
|
const toolSessionService =
|
|
@@ -232,20 +318,16 @@ export class ImageTool implements Extension {
|
|
|
232
318
|
}
|
|
233
319
|
|
|
234
320
|
deactivate(context: ExtensionContext) {
|
|
235
|
-
|
|
236
|
-
context.eventBus.off("object:modified", this.onObjectModified);
|
|
237
|
-
context.eventBus.off("selection:created", this.onSelectionChanged);
|
|
238
|
-
context.eventBus.off("selection:updated", this.onSelectionChanged);
|
|
239
|
-
context.eventBus.off("selection:cleared", this.onSelectionCleared);
|
|
240
|
-
context.eventBus.off("scene:layout:change", this.onSceneLayoutChanged);
|
|
241
|
-
context.eventBus.off("scene:geometry:change", this.onSceneGeometryChanged);
|
|
321
|
+
this.subscriptions.disposeAll();
|
|
242
322
|
this.dirtyTrackerDisposable?.dispose();
|
|
243
323
|
this.dirtyTrackerDisposable = undefined;
|
|
244
324
|
this.cropShapeHatchPattern = undefined;
|
|
245
325
|
this.cropShapeHatchPatternColor = undefined;
|
|
246
326
|
this.cropShapeHatchPatternKey = undefined;
|
|
327
|
+
this.sourceSizeCache.clear();
|
|
247
328
|
this.imageSpecs = [];
|
|
248
329
|
this.overlaySpecs = [];
|
|
330
|
+
this.imageControlsByCapabilityKey.clear();
|
|
249
331
|
|
|
250
332
|
this.clearRenderedImages();
|
|
251
333
|
this.renderProducerDisposable?.dispose();
|
|
@@ -346,6 +428,113 @@ export class ImageTool implements Extension {
|
|
|
346
428
|
);
|
|
347
429
|
}
|
|
348
430
|
|
|
431
|
+
private getEnabledImageControlCapabilities(): ImageControlCapability[] {
|
|
432
|
+
return IMAGE_DEFAULT_CONTROL_CAPABILITIES;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private getImageControls(
|
|
436
|
+
capabilities: ImageControlCapability[],
|
|
437
|
+
): Record<string, Control> {
|
|
438
|
+
const normalized = [...new Set(capabilities)].sort();
|
|
439
|
+
const cacheKey = normalized.join("|");
|
|
440
|
+
const cached = this.imageControlsByCapabilityKey.get(cacheKey);
|
|
441
|
+
if (cached) {
|
|
442
|
+
return cached;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const enabled = new Set(normalized);
|
|
446
|
+
const controls: Record<string, Control> = {};
|
|
447
|
+
IMAGE_CONTROL_DESCRIPTORS.forEach((descriptor) => {
|
|
448
|
+
if (!enabled.has(descriptor.capability)) return;
|
|
449
|
+
controls[descriptor.key] = descriptor.create();
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
this.imageControlsByCapabilityKey.set(cacheKey, controls);
|
|
453
|
+
return controls;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
private getImageControlVisualConfig(): ImageControlVisualConfig {
|
|
457
|
+
const cornerSizeRaw = Number(
|
|
458
|
+
this.getConfig<number>("image.control.cornerSize", 14) ?? 14,
|
|
459
|
+
);
|
|
460
|
+
const touchCornerSizeRaw = Number(
|
|
461
|
+
this.getConfig<number>("image.control.touchCornerSize", 24) ?? 24,
|
|
462
|
+
);
|
|
463
|
+
const borderScaleFactorRaw = Number(
|
|
464
|
+
this.getConfig<number>("image.control.borderScaleFactor", 1.5) ?? 1.5,
|
|
465
|
+
);
|
|
466
|
+
const paddingRaw = Number(
|
|
467
|
+
this.getConfig<number>("image.control.padding", 0) ?? 0,
|
|
468
|
+
);
|
|
469
|
+
const cornerStyleRaw = (this.getConfig<string>(
|
|
470
|
+
"image.control.cornerStyle",
|
|
471
|
+
"circle",
|
|
472
|
+
) || "circle") as string;
|
|
473
|
+
const cornerStyle: "rect" | "circle" =
|
|
474
|
+
cornerStyleRaw === "rect" ? "rect" : "circle";
|
|
475
|
+
|
|
476
|
+
return {
|
|
477
|
+
cornerSize: Number.isFinite(cornerSizeRaw)
|
|
478
|
+
? Math.max(4, Math.min(64, cornerSizeRaw))
|
|
479
|
+
: 14,
|
|
480
|
+
touchCornerSize: Number.isFinite(touchCornerSizeRaw)
|
|
481
|
+
? Math.max(8, Math.min(96, touchCornerSizeRaw))
|
|
482
|
+
: 24,
|
|
483
|
+
cornerStyle,
|
|
484
|
+
cornerColor:
|
|
485
|
+
this.getConfig<string>("image.control.cornerColor", "#ffffff") ||
|
|
486
|
+
"#ffffff",
|
|
487
|
+
cornerStrokeColor:
|
|
488
|
+
this.getConfig<string>("image.control.cornerStrokeColor", "#1677ff") ||
|
|
489
|
+
"#1677ff",
|
|
490
|
+
transparentCorners: !!this.getConfig<boolean>(
|
|
491
|
+
"image.control.transparentCorners",
|
|
492
|
+
false,
|
|
493
|
+
),
|
|
494
|
+
borderColor:
|
|
495
|
+
this.getConfig<string>("image.control.borderColor", "#1677ff") ||
|
|
496
|
+
"#1677ff",
|
|
497
|
+
borderScaleFactor: Number.isFinite(borderScaleFactorRaw)
|
|
498
|
+
? Math.max(0.5, Math.min(8, borderScaleFactorRaw))
|
|
499
|
+
: 1.5,
|
|
500
|
+
padding: Number.isFinite(paddingRaw)
|
|
501
|
+
? Math.max(0, Math.min(64, paddingRaw))
|
|
502
|
+
: 0,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
private applyImageObjectInteractionState(obj: any) {
|
|
507
|
+
if (!obj) return;
|
|
508
|
+
const visible = this.isImageEditingVisible();
|
|
509
|
+
const visual = this.getImageControlVisualConfig();
|
|
510
|
+
obj.set({
|
|
511
|
+
selectable: visible,
|
|
512
|
+
evented: visible,
|
|
513
|
+
hasControls: visible,
|
|
514
|
+
hasBorders: visible,
|
|
515
|
+
lockScalingFlip: true,
|
|
516
|
+
cornerSize: visual.cornerSize,
|
|
517
|
+
touchCornerSize: visual.touchCornerSize,
|
|
518
|
+
cornerStyle: visual.cornerStyle,
|
|
519
|
+
cornerColor: visual.cornerColor,
|
|
520
|
+
cornerStrokeColor: visual.cornerStrokeColor,
|
|
521
|
+
transparentCorners: visual.transparentCorners,
|
|
522
|
+
borderColor: visual.borderColor,
|
|
523
|
+
borderScaleFactor: visual.borderScaleFactor,
|
|
524
|
+
padding: visual.padding,
|
|
525
|
+
});
|
|
526
|
+
obj.controls = this.getImageControls(
|
|
527
|
+
this.getEnabledImageControlCapabilities(),
|
|
528
|
+
);
|
|
529
|
+
obj.setCoords?.();
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
private refreshImageObjectInteractionState() {
|
|
533
|
+
this.getImageObjects().forEach((obj) =>
|
|
534
|
+
this.applyImageObjectInteractionState(obj),
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
|
|
349
538
|
private isDebugEnabled(): boolean {
|
|
350
539
|
return !!this.getConfig<boolean>("image.debug", false);
|
|
351
540
|
}
|
|
@@ -377,220 +566,8 @@ export class ImageTool implements Extension {
|
|
|
377
566
|
},
|
|
378
567
|
},
|
|
379
568
|
],
|
|
380
|
-
[ContributionPointIds.CONFIGURATIONS]:
|
|
381
|
-
|
|
382
|
-
id: "image.items",
|
|
383
|
-
type: "array",
|
|
384
|
-
label: "Images",
|
|
385
|
-
default: [],
|
|
386
|
-
},
|
|
387
|
-
{
|
|
388
|
-
id: "image.debug",
|
|
389
|
-
type: "boolean",
|
|
390
|
-
label: "Image Debug Log",
|
|
391
|
-
default: false,
|
|
392
|
-
},
|
|
393
|
-
{
|
|
394
|
-
id: "image.frame.strokeColor",
|
|
395
|
-
type: "color",
|
|
396
|
-
label: "Image Frame Stroke Color",
|
|
397
|
-
default: "#808080",
|
|
398
|
-
},
|
|
399
|
-
{
|
|
400
|
-
id: "image.frame.strokeWidth",
|
|
401
|
-
type: "number",
|
|
402
|
-
label: "Image Frame Stroke Width",
|
|
403
|
-
min: 0,
|
|
404
|
-
max: 20,
|
|
405
|
-
step: 0.5,
|
|
406
|
-
default: 2,
|
|
407
|
-
},
|
|
408
|
-
{
|
|
409
|
-
id: "image.frame.strokeStyle",
|
|
410
|
-
type: "select",
|
|
411
|
-
label: "Image Frame Stroke Style",
|
|
412
|
-
options: ["solid", "dashed", "hidden"],
|
|
413
|
-
default: "dashed",
|
|
414
|
-
},
|
|
415
|
-
{
|
|
416
|
-
id: "image.frame.dashLength",
|
|
417
|
-
type: "number",
|
|
418
|
-
label: "Image Frame Dash Length",
|
|
419
|
-
min: 1,
|
|
420
|
-
max: 40,
|
|
421
|
-
step: 1,
|
|
422
|
-
default: 8,
|
|
423
|
-
},
|
|
424
|
-
{
|
|
425
|
-
id: "image.frame.innerBackground",
|
|
426
|
-
type: "color",
|
|
427
|
-
label: "Image Frame Inner Background",
|
|
428
|
-
default: "rgba(0,0,0,0)",
|
|
429
|
-
},
|
|
430
|
-
{
|
|
431
|
-
id: "image.frame.outerBackground",
|
|
432
|
-
type: "color",
|
|
433
|
-
label: "Image Frame Outer Background",
|
|
434
|
-
default: "#f5f5f5",
|
|
435
|
-
},
|
|
436
|
-
] as ConfigurationContribution[],
|
|
437
|
-
[ContributionPointIds.COMMANDS]: [
|
|
438
|
-
{
|
|
439
|
-
command: "addImage",
|
|
440
|
-
title: "Add Image",
|
|
441
|
-
handler: async (url: string, options?: Partial<ImageItem>) => {
|
|
442
|
-
const result = await this.upsertImageEntry(url, {
|
|
443
|
-
mode: "add",
|
|
444
|
-
addOptions: options,
|
|
445
|
-
});
|
|
446
|
-
return result.id;
|
|
447
|
-
},
|
|
448
|
-
},
|
|
449
|
-
{
|
|
450
|
-
command: "upsertImage",
|
|
451
|
-
title: "Upsert Image",
|
|
452
|
-
handler: async (url: string, options: UpsertImageOptions = {}) => {
|
|
453
|
-
return await this.upsertImageEntry(url, options);
|
|
454
|
-
},
|
|
455
|
-
},
|
|
456
|
-
{
|
|
457
|
-
command: "getWorkingImages",
|
|
458
|
-
title: "Get Working Images",
|
|
459
|
-
handler: () => {
|
|
460
|
-
return this.cloneItems(this.workingItems);
|
|
461
|
-
},
|
|
462
|
-
},
|
|
463
|
-
{
|
|
464
|
-
command: "setWorkingImage",
|
|
465
|
-
title: "Set Working Image",
|
|
466
|
-
handler: (id: string, updates: Partial<ImageItem>) => {
|
|
467
|
-
this.updateImageInWorking(id, updates);
|
|
468
|
-
},
|
|
469
|
-
},
|
|
470
|
-
{
|
|
471
|
-
command: "resetWorkingImages",
|
|
472
|
-
title: "Reset Working Images",
|
|
473
|
-
handler: () => {
|
|
474
|
-
this.workingItems = this.cloneItems(this.items);
|
|
475
|
-
this.hasWorkingChanges = false;
|
|
476
|
-
this.updateImages();
|
|
477
|
-
this.emitWorkingChange();
|
|
478
|
-
},
|
|
479
|
-
},
|
|
480
|
-
{
|
|
481
|
-
command: "completeImages",
|
|
482
|
-
title: "Complete Images",
|
|
483
|
-
handler: async () => {
|
|
484
|
-
return await this.commitWorkingImagesAsCropped();
|
|
485
|
-
},
|
|
486
|
-
},
|
|
487
|
-
{
|
|
488
|
-
command: "exportUserCroppedImage",
|
|
489
|
-
title: "Export User Cropped Image",
|
|
490
|
-
handler: async (options: ExportUserCroppedImageOptions = {}) => {
|
|
491
|
-
return await this.exportUserCroppedImage(options);
|
|
492
|
-
},
|
|
493
|
-
},
|
|
494
|
-
{
|
|
495
|
-
command: "fitImageToArea",
|
|
496
|
-
title: "Fit Image to Area",
|
|
497
|
-
handler: async (
|
|
498
|
-
id: string,
|
|
499
|
-
area: {
|
|
500
|
-
width: number;
|
|
501
|
-
height: number;
|
|
502
|
-
left?: number;
|
|
503
|
-
top?: number;
|
|
504
|
-
},
|
|
505
|
-
) => {
|
|
506
|
-
await this.fitImageToArea(id, area);
|
|
507
|
-
},
|
|
508
|
-
},
|
|
509
|
-
{
|
|
510
|
-
command: "fitImageToDefaultArea",
|
|
511
|
-
title: "Fit Image to Default Area",
|
|
512
|
-
handler: async (id: string) => {
|
|
513
|
-
await this.fitImageToDefaultArea(id);
|
|
514
|
-
},
|
|
515
|
-
},
|
|
516
|
-
{
|
|
517
|
-
command: "focusImage",
|
|
518
|
-
title: "Focus Image",
|
|
519
|
-
handler: (
|
|
520
|
-
id: string | null,
|
|
521
|
-
options: { syncCanvasSelection?: boolean } = {},
|
|
522
|
-
) => {
|
|
523
|
-
return this.setImageFocus(id, options);
|
|
524
|
-
},
|
|
525
|
-
},
|
|
526
|
-
{
|
|
527
|
-
command: "removeImage",
|
|
528
|
-
title: "Remove Image",
|
|
529
|
-
handler: (id: string) => {
|
|
530
|
-
const removed = this.items.find((item) => item.id === id);
|
|
531
|
-
const next = this.items.filter((item) => item.id !== id);
|
|
532
|
-
if (next.length !== this.items.length) {
|
|
533
|
-
this.purgeSourceSizeCacheForItem(removed);
|
|
534
|
-
if (this.focusedImageId === id) {
|
|
535
|
-
this.setImageFocus(null, {
|
|
536
|
-
syncCanvasSelection: true,
|
|
537
|
-
skipRender: true,
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
this.updateConfig(next);
|
|
541
|
-
}
|
|
542
|
-
},
|
|
543
|
-
},
|
|
544
|
-
{
|
|
545
|
-
command: "updateImage",
|
|
546
|
-
title: "Update Image",
|
|
547
|
-
handler: async (
|
|
548
|
-
id: string,
|
|
549
|
-
updates: Partial<ImageItem>,
|
|
550
|
-
options: UpdateImageOptions = {},
|
|
551
|
-
) => {
|
|
552
|
-
await this.updateImage(id, updates, options);
|
|
553
|
-
},
|
|
554
|
-
},
|
|
555
|
-
{
|
|
556
|
-
command: "clearImages",
|
|
557
|
-
title: "Clear Images",
|
|
558
|
-
handler: () => {
|
|
559
|
-
this.sourceSizeBySrc.clear();
|
|
560
|
-
this.setImageFocus(null, {
|
|
561
|
-
syncCanvasSelection: true,
|
|
562
|
-
skipRender: true,
|
|
563
|
-
});
|
|
564
|
-
this.updateConfig([]);
|
|
565
|
-
},
|
|
566
|
-
},
|
|
567
|
-
{
|
|
568
|
-
command: "bringToFront",
|
|
569
|
-
title: "Bring Image to Front",
|
|
570
|
-
handler: (id: string) => {
|
|
571
|
-
const index = this.items.findIndex((item) => item.id === id);
|
|
572
|
-
if (index !== -1 && index < this.items.length - 1) {
|
|
573
|
-
const next = [...this.items];
|
|
574
|
-
const [item] = next.splice(index, 1);
|
|
575
|
-
next.push(item);
|
|
576
|
-
this.updateConfig(next);
|
|
577
|
-
}
|
|
578
|
-
},
|
|
579
|
-
},
|
|
580
|
-
{
|
|
581
|
-
command: "sendToBack",
|
|
582
|
-
title: "Send Image to Back",
|
|
583
|
-
handler: (id: string) => {
|
|
584
|
-
const index = this.items.findIndex((item) => item.id === id);
|
|
585
|
-
if (index > 0) {
|
|
586
|
-
const next = [...this.items];
|
|
587
|
-
const [item] = next.splice(index, 1);
|
|
588
|
-
next.unshift(item);
|
|
589
|
-
this.updateConfig(next);
|
|
590
|
-
}
|
|
591
|
-
},
|
|
592
|
-
},
|
|
593
|
-
] as CommandContribution[],
|
|
569
|
+
[ContributionPointIds.CONFIGURATIONS]: createImageConfigurations(),
|
|
570
|
+
[ContributionPointIds.COMMANDS]: createImageCommands(this),
|
|
594
571
|
};
|
|
595
572
|
}
|
|
596
573
|
|
|
@@ -664,12 +641,7 @@ export class ImageTool implements Extension {
|
|
|
664
641
|
} else {
|
|
665
642
|
const obj = this.getImageObject(id);
|
|
666
643
|
if (obj) {
|
|
667
|
-
|
|
668
|
-
selectable: true,
|
|
669
|
-
evented: true,
|
|
670
|
-
hasControls: true,
|
|
671
|
-
hasBorders: true,
|
|
672
|
-
});
|
|
644
|
+
this.applyImageObjectInteractionState(obj);
|
|
673
645
|
canvas.setActiveObject(obj);
|
|
674
646
|
}
|
|
675
647
|
}
|
|
@@ -768,53 +740,46 @@ export class ImageTool implements Extension {
|
|
|
768
740
|
return (configService.get(key, fallback) as T) ?? fallback;
|
|
769
741
|
}
|
|
770
742
|
|
|
743
|
+
private applyCommittedItems(nextItems: ImageItem[]) {
|
|
744
|
+
const session = {
|
|
745
|
+
committed: this.items,
|
|
746
|
+
working: this.workingItems,
|
|
747
|
+
hasWorkingChanges: this.hasWorkingChanges,
|
|
748
|
+
};
|
|
749
|
+
applyCommittedSnapshot(session, this.normalizeItems(nextItems), {
|
|
750
|
+
clone: (items) => this.cloneItems(items),
|
|
751
|
+
toolActive: this.isToolActive,
|
|
752
|
+
preserveDirtyWorking: true,
|
|
753
|
+
});
|
|
754
|
+
this.items = session.committed;
|
|
755
|
+
this.workingItems = session.working;
|
|
756
|
+
this.hasWorkingChanges = session.hasWorkingChanges;
|
|
757
|
+
}
|
|
758
|
+
|
|
771
759
|
private updateConfig(newItems: ImageItem[], skipCanvasUpdate = false) {
|
|
772
760
|
if (!this.context) return;
|
|
761
|
+
this.applyCommittedItems(newItems);
|
|
762
|
+
runDeferredConfigUpdate(
|
|
763
|
+
this,
|
|
764
|
+
() => {
|
|
765
|
+
const configService = this.context?.services.get<ConfigurationService>(
|
|
766
|
+
"ConfigurationService",
|
|
767
|
+
);
|
|
768
|
+
configService?.update("image.items", this.items);
|
|
773
769
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
const configService = this.context.services.get<ConfigurationService>(
|
|
782
|
-
"ConfigurationService",
|
|
770
|
+
if (!skipCanvasUpdate) {
|
|
771
|
+
this.updateImages();
|
|
772
|
+
}
|
|
773
|
+
},
|
|
774
|
+
50,
|
|
783
775
|
);
|
|
784
|
-
configService?.update("image.items", this.items);
|
|
785
|
-
|
|
786
|
-
if (!skipCanvasUpdate) {
|
|
787
|
-
this.updateImages();
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
setTimeout(() => {
|
|
791
|
-
this.isUpdatingConfig = false;
|
|
792
|
-
}, 50);
|
|
793
776
|
}
|
|
794
777
|
|
|
795
778
|
private getFrameRect(): FrameRect {
|
|
796
|
-
if (!this.canvasService) {
|
|
797
|
-
return { left: 0, top: 0, width: 0, height: 0 };
|
|
798
|
-
}
|
|
799
779
|
const configService = this.context?.services.get<ConfigurationService>(
|
|
800
780
|
"ConfigurationService",
|
|
801
781
|
);
|
|
802
|
-
|
|
803
|
-
return { left: 0, top: 0, width: 0, height: 0 };
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
const sizeState = readSizeState(configService);
|
|
807
|
-
const layout = computeSceneLayout(this.canvasService, sizeState);
|
|
808
|
-
if (!layout) {
|
|
809
|
-
return { left: 0, top: 0, width: 0, height: 0 };
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
return this.canvasService.toSceneRect({
|
|
813
|
-
left: layout.cutRect.left,
|
|
814
|
-
top: layout.cutRect.top,
|
|
815
|
-
width: layout.cutRect.width,
|
|
816
|
-
height: layout.cutRect.height,
|
|
817
|
-
});
|
|
782
|
+
return resolveCutFrameRect(this.canvasService, configService);
|
|
818
783
|
}
|
|
819
784
|
|
|
820
785
|
private getFrameRectScreen(frame?: FrameRect): FrameRect {
|
|
@@ -825,13 +790,7 @@ export class ImageTool implements Extension {
|
|
|
825
790
|
}
|
|
826
791
|
|
|
827
792
|
private toLayoutSceneRect(rect: FrameRect): RenderLayoutRect {
|
|
828
|
-
return
|
|
829
|
-
left: rect.left,
|
|
830
|
-
top: rect.top,
|
|
831
|
-
width: rect.width,
|
|
832
|
-
height: rect.height,
|
|
833
|
-
space: "scene",
|
|
834
|
-
};
|
|
793
|
+
return toSceneLayoutRect(rect);
|
|
835
794
|
}
|
|
836
795
|
|
|
837
796
|
private async resolveDefaultFitArea(): Promise<DielineFitArea | null> {
|
|
@@ -894,26 +853,26 @@ export class ImageTool implements Extension {
|
|
|
894
853
|
const sources = [item.url, item.sourceUrl, item.committedUrl].filter(
|
|
895
854
|
(value): value is string => typeof value === "string" && value.length > 0,
|
|
896
855
|
);
|
|
897
|
-
sources.forEach((src) => this.
|
|
856
|
+
sources.forEach((src) => this.sourceSizeCache.deleteSourceSize(src));
|
|
898
857
|
}
|
|
899
858
|
|
|
900
859
|
private rememberSourceSize(src: string, obj: any) {
|
|
901
860
|
const width = Number(obj?.width || 0);
|
|
902
861
|
const height = Number(obj?.height || 0);
|
|
903
862
|
if (src && width > 0 && height > 0) {
|
|
904
|
-
this.
|
|
863
|
+
this.sourceSizeCache.rememberSourceSize(src, { width, height });
|
|
905
864
|
}
|
|
906
865
|
}
|
|
907
866
|
|
|
908
867
|
private getSourceSize(src: string, obj?: any): SourceSize {
|
|
909
|
-
const cached = src ? this.
|
|
868
|
+
const cached = src ? this.sourceSizeCache.getSourceSize(src) : undefined;
|
|
910
869
|
if (cached) return cached;
|
|
911
870
|
|
|
912
871
|
const width = Number(obj?.width || 0);
|
|
913
872
|
const height = Number(obj?.height || 0);
|
|
914
873
|
if (src && width > 0 && height > 0) {
|
|
915
874
|
const size = { width, height };
|
|
916
|
-
this.
|
|
875
|
+
this.sourceSizeCache.rememberSourceSize(src, size);
|
|
917
876
|
return size;
|
|
918
877
|
}
|
|
919
878
|
|
|
@@ -921,10 +880,10 @@ export class ImageTool implements Extension {
|
|
|
921
880
|
}
|
|
922
881
|
|
|
923
882
|
private async ensureSourceSize(src: string): Promise<SourceSize | null> {
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
if (cached) return cached;
|
|
883
|
+
return this.sourceSizeCache.ensureImageSize(src);
|
|
884
|
+
}
|
|
927
885
|
|
|
886
|
+
private async loadImageSize(src: string): Promise<SourceSize | null> {
|
|
928
887
|
try {
|
|
929
888
|
const image = await FabricImage.fromURL(src, {
|
|
930
889
|
crossOrigin: "anonymous",
|
|
@@ -932,9 +891,7 @@ export class ImageTool implements Extension {
|
|
|
932
891
|
const width = Number(image?.width || 0);
|
|
933
892
|
const height = Number(image?.height || 0);
|
|
934
893
|
if (width > 0 && height > 0) {
|
|
935
|
-
|
|
936
|
-
this.sourceSizeBySrc.set(src, size);
|
|
937
|
-
return size;
|
|
894
|
+
return { width, height };
|
|
938
895
|
}
|
|
939
896
|
} catch (error) {
|
|
940
897
|
this.debug("image:size:load-failed", {
|
|
@@ -947,11 +904,7 @@ export class ImageTool implements Extension {
|
|
|
947
904
|
}
|
|
948
905
|
|
|
949
906
|
private getCoverScale(frame: FrameRect, size: SourceSize): number {
|
|
950
|
-
|
|
951
|
-
const sh = Math.max(1, size.height);
|
|
952
|
-
const fw = Math.max(1, frame.width);
|
|
953
|
-
const fh = Math.max(1, frame.height);
|
|
954
|
-
return Math.max(fw / sw, fh / sh);
|
|
907
|
+
return getCoverScaleFromRect(frame, size);
|
|
955
908
|
}
|
|
956
909
|
|
|
957
910
|
private getFrameVisualConfig(): FrameVisualConfig {
|
|
@@ -1608,6 +1561,7 @@ export class ImageTool implements Extension {
|
|
|
1608
1561
|
this.overlaySpecs = this.buildOverlaySpecs(frame, sceneGeometry);
|
|
1609
1562
|
await this.canvasService.flushRenderFromProducers();
|
|
1610
1563
|
if (seq !== this.renderSeq) return;
|
|
1564
|
+
this.refreshImageObjectInteractionState();
|
|
1611
1565
|
|
|
1612
1566
|
renderItems.forEach((item) => {
|
|
1613
1567
|
if (!this.getImageObject(item.id)) return;
|
|
@@ -1881,7 +1835,7 @@ export class ImageTool implements Extension {
|
|
|
1881
1835
|
}),
|
|
1882
1836
|
);
|
|
1883
1837
|
if (previousCommitted && previousCommitted !== url) {
|
|
1884
|
-
this.
|
|
1838
|
+
this.sourceSizeCache.deleteSourceSize(previousCommitted);
|
|
1885
1839
|
}
|
|
1886
1840
|
}
|
|
1887
1841
|
|