@pooder/kit 6.0.0 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.test-dist/src/extensions/background.js +1 -1
- package/.test-dist/src/extensions/dieline.js +4 -0
- package/.test-dist/src/extensions/image.js +182 -7
- package/.test-dist/src/services/CanvasService.js +34 -13
- package/.test-dist/src/services/visibility.js +3 -0
- package/.test-dist/tests/run.js +2 -0
- package/CHANGELOG.md +6 -0
- package/dist/index.d.mts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +229 -20
- package/dist/index.mjs +232 -21
- package/package.json +1 -1
- package/src/extensions/dieline.ts +4 -0
- package/src/extensions/image.ts +240 -7
- package/src/services/CanvasService.ts +43 -12
- package/src/services/renderSpec.ts +2 -0
- package/src/services/visibility.ts +5 -0
- package/tests/run.ts +5 -0
package/src/extensions/image.ts
CHANGED
|
@@ -10,9 +10,11 @@ import {
|
|
|
10
10
|
} from "@pooder/core";
|
|
11
11
|
import {
|
|
12
12
|
Canvas as FabricCanvas,
|
|
13
|
+
Control,
|
|
13
14
|
Image as FabricImage,
|
|
14
15
|
Pattern,
|
|
15
16
|
Point,
|
|
17
|
+
controlsUtils,
|
|
16
18
|
} from "fabric";
|
|
17
19
|
import { CanvasService, RenderLayoutRect, RenderObjectSpec } from "../services";
|
|
18
20
|
import { isDielineShape, normalizeShapeStyle } from "./dielineShape";
|
|
@@ -66,6 +68,18 @@ interface FrameVisualConfig {
|
|
|
66
68
|
outerBackground: string;
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
interface ImageControlVisualConfig {
|
|
72
|
+
cornerSize: number;
|
|
73
|
+
touchCornerSize: number;
|
|
74
|
+
cornerStyle: "rect" | "circle";
|
|
75
|
+
cornerColor: string;
|
|
76
|
+
cornerStrokeColor: string;
|
|
77
|
+
transparentCorners: boolean;
|
|
78
|
+
borderColor: string;
|
|
79
|
+
borderScaleFactor: number;
|
|
80
|
+
padding: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
69
83
|
type ShapeOverlayShape = Exclude<DielineShape, "custom">;
|
|
70
84
|
|
|
71
85
|
interface SceneGeometryLike {
|
|
@@ -113,6 +127,45 @@ interface ExportUserCroppedImageResult {
|
|
|
113
127
|
|
|
114
128
|
const IMAGE_OBJECT_LAYER_ID = "image.user";
|
|
115
129
|
const IMAGE_OVERLAY_LAYER_ID = "image-overlay";
|
|
130
|
+
type ImageControlCapability = "rotate" | "scale" | "flipX" | "flipY";
|
|
131
|
+
|
|
132
|
+
interface ImageControlDescriptor {
|
|
133
|
+
key: string;
|
|
134
|
+
capability: ImageControlCapability;
|
|
135
|
+
create: () => Control;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const IMAGE_DEFAULT_CONTROL_CAPABILITIES: ImageControlCapability[] = [
|
|
139
|
+
"rotate",
|
|
140
|
+
"scale",
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
const IMAGE_CONTROL_DESCRIPTORS: ImageControlDescriptor[] = [
|
|
144
|
+
{
|
|
145
|
+
key: "tl",
|
|
146
|
+
capability: "rotate",
|
|
147
|
+
create: () =>
|
|
148
|
+
new Control({
|
|
149
|
+
x: -0.5,
|
|
150
|
+
y: -0.5,
|
|
151
|
+
actionName: "rotate",
|
|
152
|
+
actionHandler: controlsUtils.rotationWithSnapping,
|
|
153
|
+
cursorStyleHandler: controlsUtils.rotationStyleHandler,
|
|
154
|
+
}),
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
key: "br",
|
|
158
|
+
capability: "scale",
|
|
159
|
+
create: () =>
|
|
160
|
+
new Control({
|
|
161
|
+
x: 0.5,
|
|
162
|
+
y: 0.5,
|
|
163
|
+
actionName: "scale",
|
|
164
|
+
actionHandler: controlsUtils.scalingEqually,
|
|
165
|
+
cursorStyleHandler: controlsUtils.scaleCursorStyleHandler,
|
|
166
|
+
}),
|
|
167
|
+
},
|
|
168
|
+
];
|
|
116
169
|
|
|
117
170
|
export class ImageTool implements Extension {
|
|
118
171
|
id = "pooder.kit.image";
|
|
@@ -140,6 +193,8 @@ export class ImageTool implements Extension {
|
|
|
140
193
|
private imageSpecs: RenderObjectSpec[] = [];
|
|
141
194
|
private overlaySpecs: RenderObjectSpec[] = [];
|
|
142
195
|
private renderProducerDisposable?: { dispose: () => void };
|
|
196
|
+
private imageControlsByCapabilityKey: Map<string, Record<string, Control>> =
|
|
197
|
+
new Map();
|
|
143
198
|
|
|
144
199
|
activate(context: ExtensionContext) {
|
|
145
200
|
this.context = context;
|
|
@@ -215,7 +270,14 @@ export class ImageTool implements Extension {
|
|
|
215
270
|
return;
|
|
216
271
|
}
|
|
217
272
|
|
|
218
|
-
if (
|
|
273
|
+
if (
|
|
274
|
+
e.key.startsWith("size.") ||
|
|
275
|
+
e.key.startsWith("image.frame.") ||
|
|
276
|
+
e.key.startsWith("image.control.")
|
|
277
|
+
) {
|
|
278
|
+
if (e.key.startsWith("image.control.")) {
|
|
279
|
+
this.imageControlsByCapabilityKey.clear();
|
|
280
|
+
}
|
|
219
281
|
this.updateImages();
|
|
220
282
|
}
|
|
221
283
|
});
|
|
@@ -246,6 +308,7 @@ export class ImageTool implements Extension {
|
|
|
246
308
|
this.cropShapeHatchPatternKey = undefined;
|
|
247
309
|
this.imageSpecs = [];
|
|
248
310
|
this.overlaySpecs = [];
|
|
311
|
+
this.imageControlsByCapabilityKey.clear();
|
|
249
312
|
|
|
250
313
|
this.clearRenderedImages();
|
|
251
314
|
this.renderProducerDisposable?.dispose();
|
|
@@ -346,6 +409,113 @@ export class ImageTool implements Extension {
|
|
|
346
409
|
);
|
|
347
410
|
}
|
|
348
411
|
|
|
412
|
+
private getEnabledImageControlCapabilities(): ImageControlCapability[] {
|
|
413
|
+
return IMAGE_DEFAULT_CONTROL_CAPABILITIES;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private getImageControls(
|
|
417
|
+
capabilities: ImageControlCapability[],
|
|
418
|
+
): Record<string, Control> {
|
|
419
|
+
const normalized = [...new Set(capabilities)].sort();
|
|
420
|
+
const cacheKey = normalized.join("|");
|
|
421
|
+
const cached = this.imageControlsByCapabilityKey.get(cacheKey);
|
|
422
|
+
if (cached) {
|
|
423
|
+
return cached;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const enabled = new Set(normalized);
|
|
427
|
+
const controls: Record<string, Control> = {};
|
|
428
|
+
IMAGE_CONTROL_DESCRIPTORS.forEach((descriptor) => {
|
|
429
|
+
if (!enabled.has(descriptor.capability)) return;
|
|
430
|
+
controls[descriptor.key] = descriptor.create();
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
this.imageControlsByCapabilityKey.set(cacheKey, controls);
|
|
434
|
+
return controls;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
private getImageControlVisualConfig(): ImageControlVisualConfig {
|
|
438
|
+
const cornerSizeRaw = Number(
|
|
439
|
+
this.getConfig<number>("image.control.cornerSize", 14) ?? 14,
|
|
440
|
+
);
|
|
441
|
+
const touchCornerSizeRaw = Number(
|
|
442
|
+
this.getConfig<number>("image.control.touchCornerSize", 24) ?? 24,
|
|
443
|
+
);
|
|
444
|
+
const borderScaleFactorRaw = Number(
|
|
445
|
+
this.getConfig<number>("image.control.borderScaleFactor", 1.5) ?? 1.5,
|
|
446
|
+
);
|
|
447
|
+
const paddingRaw = Number(
|
|
448
|
+
this.getConfig<number>("image.control.padding", 0) ?? 0,
|
|
449
|
+
);
|
|
450
|
+
const cornerStyleRaw = (this.getConfig<string>(
|
|
451
|
+
"image.control.cornerStyle",
|
|
452
|
+
"circle",
|
|
453
|
+
) || "circle") as string;
|
|
454
|
+
const cornerStyle: "rect" | "circle" =
|
|
455
|
+
cornerStyleRaw === "rect" ? "rect" : "circle";
|
|
456
|
+
|
|
457
|
+
return {
|
|
458
|
+
cornerSize: Number.isFinite(cornerSizeRaw)
|
|
459
|
+
? Math.max(4, Math.min(64, cornerSizeRaw))
|
|
460
|
+
: 14,
|
|
461
|
+
touchCornerSize: Number.isFinite(touchCornerSizeRaw)
|
|
462
|
+
? Math.max(8, Math.min(96, touchCornerSizeRaw))
|
|
463
|
+
: 24,
|
|
464
|
+
cornerStyle,
|
|
465
|
+
cornerColor:
|
|
466
|
+
this.getConfig<string>("image.control.cornerColor", "#ffffff") ||
|
|
467
|
+
"#ffffff",
|
|
468
|
+
cornerStrokeColor:
|
|
469
|
+
this.getConfig<string>("image.control.cornerStrokeColor", "#1677ff") ||
|
|
470
|
+
"#1677ff",
|
|
471
|
+
transparentCorners: !!this.getConfig<boolean>(
|
|
472
|
+
"image.control.transparentCorners",
|
|
473
|
+
false,
|
|
474
|
+
),
|
|
475
|
+
borderColor:
|
|
476
|
+
this.getConfig<string>("image.control.borderColor", "#1677ff") ||
|
|
477
|
+
"#1677ff",
|
|
478
|
+
borderScaleFactor: Number.isFinite(borderScaleFactorRaw)
|
|
479
|
+
? Math.max(0.5, Math.min(8, borderScaleFactorRaw))
|
|
480
|
+
: 1.5,
|
|
481
|
+
padding: Number.isFinite(paddingRaw)
|
|
482
|
+
? Math.max(0, Math.min(64, paddingRaw))
|
|
483
|
+
: 0,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
private applyImageObjectInteractionState(obj: any) {
|
|
488
|
+
if (!obj) return;
|
|
489
|
+
const visible = this.isImageEditingVisible();
|
|
490
|
+
const visual = this.getImageControlVisualConfig();
|
|
491
|
+
obj.set({
|
|
492
|
+
selectable: visible,
|
|
493
|
+
evented: visible,
|
|
494
|
+
hasControls: visible,
|
|
495
|
+
hasBorders: visible,
|
|
496
|
+
lockScalingFlip: true,
|
|
497
|
+
cornerSize: visual.cornerSize,
|
|
498
|
+
touchCornerSize: visual.touchCornerSize,
|
|
499
|
+
cornerStyle: visual.cornerStyle,
|
|
500
|
+
cornerColor: visual.cornerColor,
|
|
501
|
+
cornerStrokeColor: visual.cornerStrokeColor,
|
|
502
|
+
transparentCorners: visual.transparentCorners,
|
|
503
|
+
borderColor: visual.borderColor,
|
|
504
|
+
borderScaleFactor: visual.borderScaleFactor,
|
|
505
|
+
padding: visual.padding,
|
|
506
|
+
});
|
|
507
|
+
obj.controls = this.getImageControls(
|
|
508
|
+
this.getEnabledImageControlCapabilities(),
|
|
509
|
+
);
|
|
510
|
+
obj.setCoords?.();
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
private refreshImageObjectInteractionState() {
|
|
514
|
+
this.getImageObjects().forEach((obj) =>
|
|
515
|
+
this.applyImageObjectInteractionState(obj),
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
|
|
349
519
|
private isDebugEnabled(): boolean {
|
|
350
520
|
return !!this.getConfig<boolean>("image.debug", false);
|
|
351
521
|
}
|
|
@@ -390,6 +560,73 @@ export class ImageTool implements Extension {
|
|
|
390
560
|
label: "Image Debug Log",
|
|
391
561
|
default: false,
|
|
392
562
|
},
|
|
563
|
+
{
|
|
564
|
+
id: "image.control.cornerSize",
|
|
565
|
+
type: "number",
|
|
566
|
+
label: "Image Control Corner Size",
|
|
567
|
+
min: 4,
|
|
568
|
+
max: 64,
|
|
569
|
+
step: 1,
|
|
570
|
+
default: 14,
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
id: "image.control.touchCornerSize",
|
|
574
|
+
type: "number",
|
|
575
|
+
label: "Image Control Touch Corner Size",
|
|
576
|
+
min: 8,
|
|
577
|
+
max: 96,
|
|
578
|
+
step: 1,
|
|
579
|
+
default: 24,
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
id: "image.control.cornerStyle",
|
|
583
|
+
type: "select",
|
|
584
|
+
label: "Image Control Corner Style",
|
|
585
|
+
options: ["circle", "rect"],
|
|
586
|
+
default: "circle",
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
id: "image.control.cornerColor",
|
|
590
|
+
type: "color",
|
|
591
|
+
label: "Image Control Corner Color",
|
|
592
|
+
default: "#ffffff",
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
id: "image.control.cornerStrokeColor",
|
|
596
|
+
type: "color",
|
|
597
|
+
label: "Image Control Corner Stroke Color",
|
|
598
|
+
default: "#1677ff",
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
id: "image.control.transparentCorners",
|
|
602
|
+
type: "boolean",
|
|
603
|
+
label: "Image Control Transparent Corners",
|
|
604
|
+
default: false,
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
id: "image.control.borderColor",
|
|
608
|
+
type: "color",
|
|
609
|
+
label: "Image Control Border Color",
|
|
610
|
+
default: "#1677ff",
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
id: "image.control.borderScaleFactor",
|
|
614
|
+
type: "number",
|
|
615
|
+
label: "Image Control Border Width",
|
|
616
|
+
min: 0.5,
|
|
617
|
+
max: 8,
|
|
618
|
+
step: 0.1,
|
|
619
|
+
default: 1.5,
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
id: "image.control.padding",
|
|
623
|
+
type: "number",
|
|
624
|
+
label: "Image Control Padding",
|
|
625
|
+
min: 0,
|
|
626
|
+
max: 64,
|
|
627
|
+
step: 1,
|
|
628
|
+
default: 0,
|
|
629
|
+
},
|
|
393
630
|
{
|
|
394
631
|
id: "image.frame.strokeColor",
|
|
395
632
|
type: "color",
|
|
@@ -664,12 +901,7 @@ export class ImageTool implements Extension {
|
|
|
664
901
|
} else {
|
|
665
902
|
const obj = this.getImageObject(id);
|
|
666
903
|
if (obj) {
|
|
667
|
-
|
|
668
|
-
selectable: true,
|
|
669
|
-
evented: true,
|
|
670
|
-
hasControls: true,
|
|
671
|
-
hasBorders: true,
|
|
672
|
-
});
|
|
904
|
+
this.applyImageObjectInteractionState(obj);
|
|
673
905
|
canvas.setActiveObject(obj);
|
|
674
906
|
}
|
|
675
907
|
}
|
|
@@ -1608,6 +1840,7 @@ export class ImageTool implements Extension {
|
|
|
1608
1840
|
this.overlaySpecs = this.buildOverlaySpecs(frame, sceneGeometry);
|
|
1609
1841
|
await this.canvasService.flushRenderFromProducers();
|
|
1610
1842
|
if (seq !== this.renderSeq) return;
|
|
1843
|
+
this.refreshImageObjectInteractionState();
|
|
1611
1844
|
|
|
1612
1845
|
renderItems.forEach((item) => {
|
|
1613
1846
|
if (!this.getImageObject(item.id)) return;
|
|
@@ -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 {
|
|
@@ -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
|
}
|
package/tests/run.ts
CHANGED
|
@@ -123,6 +123,7 @@ function testVisibilityDsl() {
|
|
|
123
123
|
const context = {
|
|
124
124
|
activeToolId: "pooder.kit.image",
|
|
125
125
|
isSessionActive: (toolId: string) => toolId === "pooder.kit.feature",
|
|
126
|
+
hasAnyActiveSession: () => true,
|
|
126
127
|
layers,
|
|
127
128
|
};
|
|
128
129
|
|
|
@@ -162,6 +163,10 @@ function testVisibilityDsl() {
|
|
|
162
163
|
) === false,
|
|
163
164
|
"sessionActive false failed",
|
|
164
165
|
);
|
|
166
|
+
assert(
|
|
167
|
+
evaluateVisibilityExpr({ op: "anySessionActive" }, context) === true,
|
|
168
|
+
"anySessionActive true failed",
|
|
169
|
+
);
|
|
165
170
|
assert(
|
|
166
171
|
evaluateVisibilityExpr(
|
|
167
172
|
{ op: "layerExists", layerId: "ruler-overlay" },
|