@pooder/kit 5.0.3 → 5.0.4
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/CHANGELOG.md +6 -0
- package/dist/index.d.mts +2 -4
- package/dist/index.d.ts +2 -4
- package/dist/index.js +81 -96
- package/dist/index.mjs +81 -96
- package/package.json +1 -1
- package/src/image.ts +93 -108
package/CHANGELOG.md
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -260,7 +260,6 @@ declare class ImageTool implements Extension {
|
|
|
260
260
|
private isToolActive;
|
|
261
261
|
private isImageSelectionActive;
|
|
262
262
|
private focusedImageId;
|
|
263
|
-
private suppressSelectionClearUntil;
|
|
264
263
|
private renderSeq;
|
|
265
264
|
private dirtyTrackerDisposable?;
|
|
266
265
|
activate(context: ExtensionContext): void;
|
|
@@ -296,8 +295,8 @@ declare class ImageTool implements Extension {
|
|
|
296
295
|
private cloneItems;
|
|
297
296
|
private emitWorkingChange;
|
|
298
297
|
private generateId;
|
|
299
|
-
private
|
|
300
|
-
private
|
|
298
|
+
private hasImageItem;
|
|
299
|
+
private setImageFocus;
|
|
301
300
|
private addImageEntry;
|
|
302
301
|
private upsertImageEntry;
|
|
303
302
|
private addItemToWorkingSessionIfNeeded;
|
|
@@ -330,7 +329,6 @@ declare class ImageTool implements Extension {
|
|
|
330
329
|
private updateImageInConfig;
|
|
331
330
|
private waitImageLoaded;
|
|
332
331
|
private refitImageToFrame;
|
|
333
|
-
private focusImageSelection;
|
|
334
332
|
private fitImageToArea;
|
|
335
333
|
private commitWorkingImagesAsCropped;
|
|
336
334
|
private exportCroppedImageByIds;
|
package/dist/index.d.ts
CHANGED
|
@@ -260,7 +260,6 @@ declare class ImageTool implements Extension {
|
|
|
260
260
|
private isToolActive;
|
|
261
261
|
private isImageSelectionActive;
|
|
262
262
|
private focusedImageId;
|
|
263
|
-
private suppressSelectionClearUntil;
|
|
264
263
|
private renderSeq;
|
|
265
264
|
private dirtyTrackerDisposable?;
|
|
266
265
|
activate(context: ExtensionContext): void;
|
|
@@ -296,8 +295,8 @@ declare class ImageTool implements Extension {
|
|
|
296
295
|
private cloneItems;
|
|
297
296
|
private emitWorkingChange;
|
|
298
297
|
private generateId;
|
|
299
|
-
private
|
|
300
|
-
private
|
|
298
|
+
private hasImageItem;
|
|
299
|
+
private setImageFocus;
|
|
301
300
|
private addImageEntry;
|
|
302
301
|
private upsertImageEntry;
|
|
303
302
|
private addItemToWorkingSessionIfNeeded;
|
|
@@ -330,7 +329,6 @@ declare class ImageTool implements Extension {
|
|
|
330
329
|
private updateImageInConfig;
|
|
331
330
|
private waitImageLoaded;
|
|
332
331
|
private refitImageToFrame;
|
|
333
|
-
private focusImageSelection;
|
|
334
332
|
private fitImageToArea;
|
|
335
333
|
private commitWorkingImagesAsCropped;
|
|
336
334
|
private exportCroppedImageByIds;
|
package/dist/index.js
CHANGED
|
@@ -3790,7 +3790,6 @@ var import_core5 = require("@pooder/core");
|
|
|
3790
3790
|
var import_fabric5 = require("fabric");
|
|
3791
3791
|
var IMAGE_OBJECT_LAYER_ID2 = "image.user";
|
|
3792
3792
|
var IMAGE_OVERLAY_LAYER_ID = "image-overlay";
|
|
3793
|
-
var IMAGE_REPLACE_GUARD_MS = 2500;
|
|
3794
3793
|
var ImageTool = class {
|
|
3795
3794
|
constructor() {
|
|
3796
3795
|
this.id = "pooder.kit.image";
|
|
@@ -3806,18 +3805,15 @@ var ImageTool = class {
|
|
|
3806
3805
|
this.isToolActive = false;
|
|
3807
3806
|
this.isImageSelectionActive = false;
|
|
3808
3807
|
this.focusedImageId = null;
|
|
3809
|
-
this.suppressSelectionClearUntil = 0;
|
|
3810
3808
|
this.renderSeq = 0;
|
|
3811
3809
|
this.onToolActivated = (event) => {
|
|
3812
3810
|
const before = this.isToolActive;
|
|
3813
3811
|
this.syncToolActiveFromWorkbench(event.id);
|
|
3814
3812
|
if (!this.isToolActive) {
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
this.focusedImageId = null;
|
|
3820
|
-
}
|
|
3813
|
+
this.setImageFocus(null, {
|
|
3814
|
+
syncCanvasSelection: true,
|
|
3815
|
+
skipRender: true
|
|
3816
|
+
});
|
|
3821
3817
|
}
|
|
3822
3818
|
this.debug("tool:activated", {
|
|
3823
3819
|
id: event.id,
|
|
@@ -3825,8 +3821,7 @@ var ImageTool = class {
|
|
|
3825
3821
|
reason: event.reason,
|
|
3826
3822
|
before,
|
|
3827
3823
|
isToolActive: this.isToolActive,
|
|
3828
|
-
focusedImageId: this.focusedImageId
|
|
3829
|
-
suppressSelectionClearUntil: this.suppressSelectionClearUntil
|
|
3824
|
+
focusedImageId: this.focusedImageId
|
|
3830
3825
|
});
|
|
3831
3826
|
if (!this.isToolActive && this.isDebugEnabled()) {
|
|
3832
3827
|
console.trace("[ImageTool] tool deactivated trace");
|
|
@@ -3865,16 +3860,10 @@ var ImageTool = class {
|
|
|
3865
3860
|
this.updateImages();
|
|
3866
3861
|
};
|
|
3867
3862
|
this.onSelectionCleared = () => {
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
focusedImageId: this.focusedImageId
|
|
3873
|
-
});
|
|
3874
|
-
return;
|
|
3875
|
-
}
|
|
3876
|
-
this.isImageSelectionActive = false;
|
|
3877
|
-
this.focusedImageId = null;
|
|
3863
|
+
this.setImageFocus(null, {
|
|
3864
|
+
syncCanvasSelection: false,
|
|
3865
|
+
skipRender: true
|
|
3866
|
+
});
|
|
3878
3867
|
this.debug("selection:cleared applied");
|
|
3879
3868
|
this.updateImages();
|
|
3880
3869
|
};
|
|
@@ -4030,7 +4019,7 @@ var ImageTool = class {
|
|
|
4030
4019
|
id: "image.frame.strokeColor",
|
|
4031
4020
|
type: "color",
|
|
4032
4021
|
label: "Image Frame Stroke Color",
|
|
4033
|
-
default: "#
|
|
4022
|
+
default: "#808080"
|
|
4034
4023
|
},
|
|
4035
4024
|
{
|
|
4036
4025
|
id: "image.frame.strokeWidth",
|
|
@@ -4046,7 +4035,7 @@ var ImageTool = class {
|
|
|
4046
4035
|
type: "select",
|
|
4047
4036
|
label: "Image Frame Stroke Style",
|
|
4048
4037
|
options: ["solid", "dashed", "hidden"],
|
|
4049
|
-
default: "
|
|
4038
|
+
default: "dashed"
|
|
4050
4039
|
},
|
|
4051
4040
|
{
|
|
4052
4041
|
id: "image.frame.dashLength",
|
|
@@ -4141,6 +4130,13 @@ var ImageTool = class {
|
|
|
4141
4130
|
await this.fitImageToDefaultArea(id);
|
|
4142
4131
|
}
|
|
4143
4132
|
},
|
|
4133
|
+
{
|
|
4134
|
+
command: "focusImage",
|
|
4135
|
+
title: "Focus Image",
|
|
4136
|
+
handler: (id, options = {}) => {
|
|
4137
|
+
return this.setImageFocus(id, options);
|
|
4138
|
+
}
|
|
4139
|
+
},
|
|
4144
4140
|
{
|
|
4145
4141
|
command: "removeImage",
|
|
4146
4142
|
title: "Remove Image",
|
|
@@ -4150,8 +4146,10 @@ var ImageTool = class {
|
|
|
4150
4146
|
if (next.length !== this.items.length) {
|
|
4151
4147
|
this.purgeSourceSizeCacheForItem(removed);
|
|
4152
4148
|
if (this.focusedImageId === id) {
|
|
4153
|
-
this.
|
|
4154
|
-
|
|
4149
|
+
this.setImageFocus(null, {
|
|
4150
|
+
syncCanvasSelection: true,
|
|
4151
|
+
skipRender: true
|
|
4152
|
+
});
|
|
4155
4153
|
}
|
|
4156
4154
|
this.updateConfig(next);
|
|
4157
4155
|
}
|
|
@@ -4169,8 +4167,10 @@ var ImageTool = class {
|
|
|
4169
4167
|
title: "Clear Images",
|
|
4170
4168
|
handler: () => {
|
|
4171
4169
|
this.sourceSizeBySrc.clear();
|
|
4172
|
-
this.
|
|
4173
|
-
|
|
4170
|
+
this.setImageFocus(null, {
|
|
4171
|
+
syncCanvasSelection: true,
|
|
4172
|
+
skipRender: true
|
|
4173
|
+
});
|
|
4174
4174
|
this.updateConfig([]);
|
|
4175
4175
|
}
|
|
4176
4176
|
},
|
|
@@ -4235,22 +4235,38 @@ var ImageTool = class {
|
|
|
4235
4235
|
generateId() {
|
|
4236
4236
|
return Math.random().toString(36).substring(2, 9);
|
|
4237
4237
|
}
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
const active = (_a = this.canvasService) == null ? void 0 : _a.canvas.getActiveObject();
|
|
4241
|
-
if (((_b = active == null ? void 0 : active.data) == null ? void 0 : _b.layerId) === IMAGE_OBJECT_LAYER_ID2 && typeof ((_c = active == null ? void 0 : active.data) == null ? void 0 : _c.id) === "string") {
|
|
4242
|
-
return active.data.id;
|
|
4243
|
-
}
|
|
4244
|
-
return null;
|
|
4238
|
+
hasImageItem(id) {
|
|
4239
|
+
return this.items.some((item) => item.id === id) || this.workingItems.some((item) => item.id === id);
|
|
4245
4240
|
}
|
|
4246
|
-
|
|
4247
|
-
const
|
|
4248
|
-
if (
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4241
|
+
setImageFocus(id, options = {}) {
|
|
4242
|
+
const syncCanvasSelection = options.syncCanvasSelection !== false;
|
|
4243
|
+
if (id && !this.hasImageItem(id)) {
|
|
4244
|
+
return { ok: false, reason: "image-not-found" };
|
|
4245
|
+
}
|
|
4246
|
+
this.focusedImageId = id;
|
|
4247
|
+
this.isImageSelectionActive = !!id;
|
|
4248
|
+
if (syncCanvasSelection && this.canvasService) {
|
|
4249
|
+
const canvas = this.canvasService.canvas;
|
|
4250
|
+
if (!id) {
|
|
4251
|
+
canvas.discardActiveObject();
|
|
4252
|
+
} else {
|
|
4253
|
+
const obj = this.getImageObject(id);
|
|
4254
|
+
if (obj) {
|
|
4255
|
+
obj.set({
|
|
4256
|
+
selectable: true,
|
|
4257
|
+
evented: true,
|
|
4258
|
+
hasControls: true,
|
|
4259
|
+
hasBorders: true
|
|
4260
|
+
});
|
|
4261
|
+
canvas.setActiveObject(obj);
|
|
4262
|
+
}
|
|
4263
|
+
}
|
|
4264
|
+
this.canvasService.requestRenderAll();
|
|
4265
|
+
}
|
|
4266
|
+
if (!options.skipRender) {
|
|
4267
|
+
this.updateImages();
|
|
4268
|
+
}
|
|
4269
|
+
return { ok: true, id };
|
|
4254
4270
|
}
|
|
4255
4271
|
async addImageEntry(url, options, fitOnAdd = true) {
|
|
4256
4272
|
const id = this.generateId();
|
|
@@ -4260,9 +4276,6 @@ var ImageTool = class {
|
|
|
4260
4276
|
opacity: 1,
|
|
4261
4277
|
...options
|
|
4262
4278
|
});
|
|
4263
|
-
this.focusedImageId = id;
|
|
4264
|
-
this.isImageSelectionActive = true;
|
|
4265
|
-
this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
|
|
4266
4279
|
const sessionDirtyBeforeAdd = this.isToolActive && this.hasWorkingChanges;
|
|
4267
4280
|
const waitLoaded = this.waitImageLoaded(id, true);
|
|
4268
4281
|
this.updateConfig([...this.items, newItem]);
|
|
@@ -4272,26 +4285,24 @@ var ImageTool = class {
|
|
|
4272
4285
|
await this.fitImageToDefaultArea(id);
|
|
4273
4286
|
}
|
|
4274
4287
|
if (loaded) {
|
|
4275
|
-
this.
|
|
4288
|
+
this.setImageFocus(id);
|
|
4276
4289
|
}
|
|
4277
4290
|
return id;
|
|
4278
4291
|
}
|
|
4279
4292
|
async upsertImageEntry(url, options = {}) {
|
|
4280
|
-
|
|
4281
|
-
const mode = options.mode || "auto";
|
|
4293
|
+
const mode = options.mode || (options.id ? "replace" : "add");
|
|
4282
4294
|
const fitOnAdd = options.fitOnAdd !== false;
|
|
4283
|
-
if (mode === "
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4295
|
+
if (mode === "replace") {
|
|
4296
|
+
if (!options.id) {
|
|
4297
|
+
throw new Error("replace-target-id-required");
|
|
4298
|
+
}
|
|
4299
|
+
const targetId = options.id;
|
|
4300
|
+
if (!this.hasImageItem(targetId)) {
|
|
4301
|
+
throw new Error("replace-target-not-found");
|
|
4302
|
+
}
|
|
4289
4303
|
await this.updateImageInConfig(targetId, { url });
|
|
4290
4304
|
return { id: targetId, mode: "replace" };
|
|
4291
4305
|
}
|
|
4292
|
-
if (mode === "replace" || options.createIfMissing === false) {
|
|
4293
|
-
throw new Error("replace-target-not-found");
|
|
4294
|
-
}
|
|
4295
4306
|
const id = await this.addImageEntry(url, options.addOptions, fitOnAdd);
|
|
4296
4307
|
return { id, mode: "add" };
|
|
4297
4308
|
}
|
|
@@ -4466,9 +4477,9 @@ var ImageTool = class {
|
|
|
4466
4477
|
var _a, _b;
|
|
4467
4478
|
const strokeStyleRaw = this.getConfig(
|
|
4468
4479
|
"image.frame.strokeStyle",
|
|
4469
|
-
"
|
|
4470
|
-
) || "
|
|
4471
|
-
const strokeStyle = strokeStyleRaw === "dashed" || strokeStyleRaw === "hidden" ? strokeStyleRaw : "
|
|
4480
|
+
"dashed"
|
|
4481
|
+
) || "dashed";
|
|
4482
|
+
const strokeStyle = strokeStyleRaw === "dashed" || strokeStyleRaw === "hidden" ? strokeStyleRaw : "dashed";
|
|
4472
4483
|
const strokeWidth = Number(
|
|
4473
4484
|
(_a = this.getConfig("image.frame.strokeWidth", 2)) != null ? _a : 2
|
|
4474
4485
|
);
|
|
@@ -4476,7 +4487,7 @@ var ImageTool = class {
|
|
|
4476
4487
|
(_b = this.getConfig("image.frame.dashLength", 8)) != null ? _b : 8
|
|
4477
4488
|
);
|
|
4478
4489
|
return {
|
|
4479
|
-
strokeColor: this.getConfig("image.frame.strokeColor", "#
|
|
4490
|
+
strokeColor: this.getConfig("image.frame.strokeColor", "#808080") || "#808080",
|
|
4480
4491
|
strokeWidth: Number.isFinite(strokeWidth) ? Math.max(0, strokeWidth) : 2,
|
|
4481
4492
|
strokeStyle,
|
|
4482
4493
|
dashLength: Number.isFinite(dashLength) ? Math.max(1, dashLength) : 8,
|
|
@@ -4741,8 +4752,10 @@ var ImageTool = class {
|
|
|
4741
4752
|
const frame = this.getFrameRect();
|
|
4742
4753
|
const desiredIds = new Set(renderItems.map((item) => item.id));
|
|
4743
4754
|
if (this.focusedImageId && !desiredIds.has(this.focusedImageId)) {
|
|
4744
|
-
this.
|
|
4745
|
-
|
|
4755
|
+
this.setImageFocus(null, {
|
|
4756
|
+
syncCanvasSelection: false,
|
|
4757
|
+
skipRender: true
|
|
4758
|
+
});
|
|
4746
4759
|
}
|
|
4747
4760
|
this.getImageObjects().forEach((obj) => {
|
|
4748
4761
|
var _a, _b;
|
|
@@ -4785,8 +4798,10 @@ var ImageTool = class {
|
|
|
4785
4798
|
next[index] = this.normalizeItem({ ...next[index], ...updates });
|
|
4786
4799
|
this.workingItems = next;
|
|
4787
4800
|
this.hasWorkingChanges = true;
|
|
4788
|
-
this.
|
|
4789
|
-
|
|
4801
|
+
this.setImageFocus(id, {
|
|
4802
|
+
syncCanvasSelection: false,
|
|
4803
|
+
skipRender: true
|
|
4804
|
+
});
|
|
4790
4805
|
if (this.isToolActive) {
|
|
4791
4806
|
this.updateImages();
|
|
4792
4807
|
}
|
|
@@ -4815,16 +4830,13 @@ var ImageTool = class {
|
|
|
4815
4830
|
});
|
|
4816
4831
|
this.updateConfig(next);
|
|
4817
4832
|
if (replacingSource) {
|
|
4818
|
-
this.focusedImageId = id;
|
|
4819
|
-
this.isImageSelectionActive = true;
|
|
4820
|
-
this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
|
|
4821
4833
|
this.debug("replace:image:begin", { id, replacingUrl });
|
|
4822
4834
|
this.purgeSourceSizeCacheForItem(base);
|
|
4823
4835
|
const loaded = await this.waitImageLoaded(id, true);
|
|
4824
4836
|
this.debug("replace:image:loaded", { id, loaded });
|
|
4825
4837
|
if (loaded) {
|
|
4826
4838
|
await this.refitImageToFrame(id);
|
|
4827
|
-
this.
|
|
4839
|
+
this.setImageFocus(id);
|
|
4828
4840
|
}
|
|
4829
4841
|
}
|
|
4830
4842
|
}
|
|
@@ -4868,29 +4880,9 @@ var ImageTool = class {
|
|
|
4868
4880
|
this.updateConfig(next);
|
|
4869
4881
|
this.workingItems = this.cloneItems(next);
|
|
4870
4882
|
this.hasWorkingChanges = false;
|
|
4871
|
-
this.isImageSelectionActive = true;
|
|
4872
|
-
this.focusedImageId = id;
|
|
4873
4883
|
this.updateImages();
|
|
4874
4884
|
this.emitWorkingChange(id);
|
|
4875
4885
|
}
|
|
4876
|
-
focusImageSelection(id) {
|
|
4877
|
-
if (!this.canvasService) return;
|
|
4878
|
-
const obj = this.getImageObject(id);
|
|
4879
|
-
if (!obj) return;
|
|
4880
|
-
this.isImageSelectionActive = true;
|
|
4881
|
-
this.focusedImageId = id;
|
|
4882
|
-
this.suppressSelectionClearUntil = Date.now() + 700;
|
|
4883
|
-
obj.set({
|
|
4884
|
-
selectable: true,
|
|
4885
|
-
evented: true,
|
|
4886
|
-
hasControls: true,
|
|
4887
|
-
hasBorders: true
|
|
4888
|
-
});
|
|
4889
|
-
this.canvasService.canvas.setActiveObject(obj);
|
|
4890
|
-
this.debug("focus:image", { id });
|
|
4891
|
-
this.canvasService.requestRenderAll();
|
|
4892
|
-
this.updateImages();
|
|
4893
|
-
}
|
|
4894
4886
|
async fitImageToArea(id, area) {
|
|
4895
4887
|
var _a, _b;
|
|
4896
4888
|
if (!this.canvasService) return;
|
|
@@ -4940,7 +4932,6 @@ var ImageTool = class {
|
|
|
4940
4932
|
if (!frame.width || !frame.height) {
|
|
4941
4933
|
return { ok: false, reason: "frame-not-ready" };
|
|
4942
4934
|
}
|
|
4943
|
-
const focusId = this.resolveReplaceTargetId(this.focusedImageId) || (this.workingItems.length === 1 ? this.workingItems[0].id : null);
|
|
4944
4935
|
const next = [];
|
|
4945
4936
|
for (const item of this.workingItems) {
|
|
4946
4937
|
const url = await this.exportCroppedImageByIds([item.id], {
|
|
@@ -4964,13 +4955,7 @@ var ImageTool = class {
|
|
|
4964
4955
|
this.hasWorkingChanges = false;
|
|
4965
4956
|
this.workingItems = this.cloneItems(next);
|
|
4966
4957
|
this.updateConfig(next);
|
|
4967
|
-
this.emitWorkingChange(
|
|
4968
|
-
if (focusId) {
|
|
4969
|
-
this.focusedImageId = focusId;
|
|
4970
|
-
this.isImageSelectionActive = true;
|
|
4971
|
-
this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
|
|
4972
|
-
this.focusImageSelection(focusId);
|
|
4973
|
-
}
|
|
4958
|
+
this.emitWorkingChange(this.focusedImageId);
|
|
4974
4959
|
return { ok: true };
|
|
4975
4960
|
}
|
|
4976
4961
|
async exportCroppedImageByIds(imageIds, options) {
|
package/dist/index.mjs
CHANGED
|
@@ -3741,7 +3741,6 @@ import {
|
|
|
3741
3741
|
import { Canvas as FabricCanvas2, Image as FabricImage, Point as Point2 } from "fabric";
|
|
3742
3742
|
var IMAGE_OBJECT_LAYER_ID2 = "image.user";
|
|
3743
3743
|
var IMAGE_OVERLAY_LAYER_ID = "image-overlay";
|
|
3744
|
-
var IMAGE_REPLACE_GUARD_MS = 2500;
|
|
3745
3744
|
var ImageTool = class {
|
|
3746
3745
|
constructor() {
|
|
3747
3746
|
this.id = "pooder.kit.image";
|
|
@@ -3757,18 +3756,15 @@ var ImageTool = class {
|
|
|
3757
3756
|
this.isToolActive = false;
|
|
3758
3757
|
this.isImageSelectionActive = false;
|
|
3759
3758
|
this.focusedImageId = null;
|
|
3760
|
-
this.suppressSelectionClearUntil = 0;
|
|
3761
3759
|
this.renderSeq = 0;
|
|
3762
3760
|
this.onToolActivated = (event) => {
|
|
3763
3761
|
const before = this.isToolActive;
|
|
3764
3762
|
this.syncToolActiveFromWorkbench(event.id);
|
|
3765
3763
|
if (!this.isToolActive) {
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
this.focusedImageId = null;
|
|
3771
|
-
}
|
|
3764
|
+
this.setImageFocus(null, {
|
|
3765
|
+
syncCanvasSelection: true,
|
|
3766
|
+
skipRender: true
|
|
3767
|
+
});
|
|
3772
3768
|
}
|
|
3773
3769
|
this.debug("tool:activated", {
|
|
3774
3770
|
id: event.id,
|
|
@@ -3776,8 +3772,7 @@ var ImageTool = class {
|
|
|
3776
3772
|
reason: event.reason,
|
|
3777
3773
|
before,
|
|
3778
3774
|
isToolActive: this.isToolActive,
|
|
3779
|
-
focusedImageId: this.focusedImageId
|
|
3780
|
-
suppressSelectionClearUntil: this.suppressSelectionClearUntil
|
|
3775
|
+
focusedImageId: this.focusedImageId
|
|
3781
3776
|
});
|
|
3782
3777
|
if (!this.isToolActive && this.isDebugEnabled()) {
|
|
3783
3778
|
console.trace("[ImageTool] tool deactivated trace");
|
|
@@ -3816,16 +3811,10 @@ var ImageTool = class {
|
|
|
3816
3811
|
this.updateImages();
|
|
3817
3812
|
};
|
|
3818
3813
|
this.onSelectionCleared = () => {
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
focusedImageId: this.focusedImageId
|
|
3824
|
-
});
|
|
3825
|
-
return;
|
|
3826
|
-
}
|
|
3827
|
-
this.isImageSelectionActive = false;
|
|
3828
|
-
this.focusedImageId = null;
|
|
3814
|
+
this.setImageFocus(null, {
|
|
3815
|
+
syncCanvasSelection: false,
|
|
3816
|
+
skipRender: true
|
|
3817
|
+
});
|
|
3829
3818
|
this.debug("selection:cleared applied");
|
|
3830
3819
|
this.updateImages();
|
|
3831
3820
|
};
|
|
@@ -3981,7 +3970,7 @@ var ImageTool = class {
|
|
|
3981
3970
|
id: "image.frame.strokeColor",
|
|
3982
3971
|
type: "color",
|
|
3983
3972
|
label: "Image Frame Stroke Color",
|
|
3984
|
-
default: "#
|
|
3973
|
+
default: "#808080"
|
|
3985
3974
|
},
|
|
3986
3975
|
{
|
|
3987
3976
|
id: "image.frame.strokeWidth",
|
|
@@ -3997,7 +3986,7 @@ var ImageTool = class {
|
|
|
3997
3986
|
type: "select",
|
|
3998
3987
|
label: "Image Frame Stroke Style",
|
|
3999
3988
|
options: ["solid", "dashed", "hidden"],
|
|
4000
|
-
default: "
|
|
3989
|
+
default: "dashed"
|
|
4001
3990
|
},
|
|
4002
3991
|
{
|
|
4003
3992
|
id: "image.frame.dashLength",
|
|
@@ -4092,6 +4081,13 @@ var ImageTool = class {
|
|
|
4092
4081
|
await this.fitImageToDefaultArea(id);
|
|
4093
4082
|
}
|
|
4094
4083
|
},
|
|
4084
|
+
{
|
|
4085
|
+
command: "focusImage",
|
|
4086
|
+
title: "Focus Image",
|
|
4087
|
+
handler: (id, options = {}) => {
|
|
4088
|
+
return this.setImageFocus(id, options);
|
|
4089
|
+
}
|
|
4090
|
+
},
|
|
4095
4091
|
{
|
|
4096
4092
|
command: "removeImage",
|
|
4097
4093
|
title: "Remove Image",
|
|
@@ -4101,8 +4097,10 @@ var ImageTool = class {
|
|
|
4101
4097
|
if (next.length !== this.items.length) {
|
|
4102
4098
|
this.purgeSourceSizeCacheForItem(removed);
|
|
4103
4099
|
if (this.focusedImageId === id) {
|
|
4104
|
-
this.
|
|
4105
|
-
|
|
4100
|
+
this.setImageFocus(null, {
|
|
4101
|
+
syncCanvasSelection: true,
|
|
4102
|
+
skipRender: true
|
|
4103
|
+
});
|
|
4106
4104
|
}
|
|
4107
4105
|
this.updateConfig(next);
|
|
4108
4106
|
}
|
|
@@ -4120,8 +4118,10 @@ var ImageTool = class {
|
|
|
4120
4118
|
title: "Clear Images",
|
|
4121
4119
|
handler: () => {
|
|
4122
4120
|
this.sourceSizeBySrc.clear();
|
|
4123
|
-
this.
|
|
4124
|
-
|
|
4121
|
+
this.setImageFocus(null, {
|
|
4122
|
+
syncCanvasSelection: true,
|
|
4123
|
+
skipRender: true
|
|
4124
|
+
});
|
|
4125
4125
|
this.updateConfig([]);
|
|
4126
4126
|
}
|
|
4127
4127
|
},
|
|
@@ -4186,22 +4186,38 @@ var ImageTool = class {
|
|
|
4186
4186
|
generateId() {
|
|
4187
4187
|
return Math.random().toString(36).substring(2, 9);
|
|
4188
4188
|
}
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
const active = (_a = this.canvasService) == null ? void 0 : _a.canvas.getActiveObject();
|
|
4192
|
-
if (((_b = active == null ? void 0 : active.data) == null ? void 0 : _b.layerId) === IMAGE_OBJECT_LAYER_ID2 && typeof ((_c = active == null ? void 0 : active.data) == null ? void 0 : _c.id) === "string") {
|
|
4193
|
-
return active.data.id;
|
|
4194
|
-
}
|
|
4195
|
-
return null;
|
|
4189
|
+
hasImageItem(id) {
|
|
4190
|
+
return this.items.some((item) => item.id === id) || this.workingItems.some((item) => item.id === id);
|
|
4196
4191
|
}
|
|
4197
|
-
|
|
4198
|
-
const
|
|
4199
|
-
if (
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4192
|
+
setImageFocus(id, options = {}) {
|
|
4193
|
+
const syncCanvasSelection = options.syncCanvasSelection !== false;
|
|
4194
|
+
if (id && !this.hasImageItem(id)) {
|
|
4195
|
+
return { ok: false, reason: "image-not-found" };
|
|
4196
|
+
}
|
|
4197
|
+
this.focusedImageId = id;
|
|
4198
|
+
this.isImageSelectionActive = !!id;
|
|
4199
|
+
if (syncCanvasSelection && this.canvasService) {
|
|
4200
|
+
const canvas = this.canvasService.canvas;
|
|
4201
|
+
if (!id) {
|
|
4202
|
+
canvas.discardActiveObject();
|
|
4203
|
+
} else {
|
|
4204
|
+
const obj = this.getImageObject(id);
|
|
4205
|
+
if (obj) {
|
|
4206
|
+
obj.set({
|
|
4207
|
+
selectable: true,
|
|
4208
|
+
evented: true,
|
|
4209
|
+
hasControls: true,
|
|
4210
|
+
hasBorders: true
|
|
4211
|
+
});
|
|
4212
|
+
canvas.setActiveObject(obj);
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
this.canvasService.requestRenderAll();
|
|
4216
|
+
}
|
|
4217
|
+
if (!options.skipRender) {
|
|
4218
|
+
this.updateImages();
|
|
4219
|
+
}
|
|
4220
|
+
return { ok: true, id };
|
|
4205
4221
|
}
|
|
4206
4222
|
async addImageEntry(url, options, fitOnAdd = true) {
|
|
4207
4223
|
const id = this.generateId();
|
|
@@ -4211,9 +4227,6 @@ var ImageTool = class {
|
|
|
4211
4227
|
opacity: 1,
|
|
4212
4228
|
...options
|
|
4213
4229
|
});
|
|
4214
|
-
this.focusedImageId = id;
|
|
4215
|
-
this.isImageSelectionActive = true;
|
|
4216
|
-
this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
|
|
4217
4230
|
const sessionDirtyBeforeAdd = this.isToolActive && this.hasWorkingChanges;
|
|
4218
4231
|
const waitLoaded = this.waitImageLoaded(id, true);
|
|
4219
4232
|
this.updateConfig([...this.items, newItem]);
|
|
@@ -4223,26 +4236,24 @@ var ImageTool = class {
|
|
|
4223
4236
|
await this.fitImageToDefaultArea(id);
|
|
4224
4237
|
}
|
|
4225
4238
|
if (loaded) {
|
|
4226
|
-
this.
|
|
4239
|
+
this.setImageFocus(id);
|
|
4227
4240
|
}
|
|
4228
4241
|
return id;
|
|
4229
4242
|
}
|
|
4230
4243
|
async upsertImageEntry(url, options = {}) {
|
|
4231
|
-
|
|
4232
|
-
const mode = options.mode || "auto";
|
|
4244
|
+
const mode = options.mode || (options.id ? "replace" : "add");
|
|
4233
4245
|
const fitOnAdd = options.fitOnAdd !== false;
|
|
4234
|
-
if (mode === "
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4246
|
+
if (mode === "replace") {
|
|
4247
|
+
if (!options.id) {
|
|
4248
|
+
throw new Error("replace-target-id-required");
|
|
4249
|
+
}
|
|
4250
|
+
const targetId = options.id;
|
|
4251
|
+
if (!this.hasImageItem(targetId)) {
|
|
4252
|
+
throw new Error("replace-target-not-found");
|
|
4253
|
+
}
|
|
4240
4254
|
await this.updateImageInConfig(targetId, { url });
|
|
4241
4255
|
return { id: targetId, mode: "replace" };
|
|
4242
4256
|
}
|
|
4243
|
-
if (mode === "replace" || options.createIfMissing === false) {
|
|
4244
|
-
throw new Error("replace-target-not-found");
|
|
4245
|
-
}
|
|
4246
4257
|
const id = await this.addImageEntry(url, options.addOptions, fitOnAdd);
|
|
4247
4258
|
return { id, mode: "add" };
|
|
4248
4259
|
}
|
|
@@ -4417,9 +4428,9 @@ var ImageTool = class {
|
|
|
4417
4428
|
var _a, _b;
|
|
4418
4429
|
const strokeStyleRaw = this.getConfig(
|
|
4419
4430
|
"image.frame.strokeStyle",
|
|
4420
|
-
"
|
|
4421
|
-
) || "
|
|
4422
|
-
const strokeStyle = strokeStyleRaw === "dashed" || strokeStyleRaw === "hidden" ? strokeStyleRaw : "
|
|
4431
|
+
"dashed"
|
|
4432
|
+
) || "dashed";
|
|
4433
|
+
const strokeStyle = strokeStyleRaw === "dashed" || strokeStyleRaw === "hidden" ? strokeStyleRaw : "dashed";
|
|
4423
4434
|
const strokeWidth = Number(
|
|
4424
4435
|
(_a = this.getConfig("image.frame.strokeWidth", 2)) != null ? _a : 2
|
|
4425
4436
|
);
|
|
@@ -4427,7 +4438,7 @@ var ImageTool = class {
|
|
|
4427
4438
|
(_b = this.getConfig("image.frame.dashLength", 8)) != null ? _b : 8
|
|
4428
4439
|
);
|
|
4429
4440
|
return {
|
|
4430
|
-
strokeColor: this.getConfig("image.frame.strokeColor", "#
|
|
4441
|
+
strokeColor: this.getConfig("image.frame.strokeColor", "#808080") || "#808080",
|
|
4431
4442
|
strokeWidth: Number.isFinite(strokeWidth) ? Math.max(0, strokeWidth) : 2,
|
|
4432
4443
|
strokeStyle,
|
|
4433
4444
|
dashLength: Number.isFinite(dashLength) ? Math.max(1, dashLength) : 8,
|
|
@@ -4692,8 +4703,10 @@ var ImageTool = class {
|
|
|
4692
4703
|
const frame = this.getFrameRect();
|
|
4693
4704
|
const desiredIds = new Set(renderItems.map((item) => item.id));
|
|
4694
4705
|
if (this.focusedImageId && !desiredIds.has(this.focusedImageId)) {
|
|
4695
|
-
this.
|
|
4696
|
-
|
|
4706
|
+
this.setImageFocus(null, {
|
|
4707
|
+
syncCanvasSelection: false,
|
|
4708
|
+
skipRender: true
|
|
4709
|
+
});
|
|
4697
4710
|
}
|
|
4698
4711
|
this.getImageObjects().forEach((obj) => {
|
|
4699
4712
|
var _a, _b;
|
|
@@ -4736,8 +4749,10 @@ var ImageTool = class {
|
|
|
4736
4749
|
next[index] = this.normalizeItem({ ...next[index], ...updates });
|
|
4737
4750
|
this.workingItems = next;
|
|
4738
4751
|
this.hasWorkingChanges = true;
|
|
4739
|
-
this.
|
|
4740
|
-
|
|
4752
|
+
this.setImageFocus(id, {
|
|
4753
|
+
syncCanvasSelection: false,
|
|
4754
|
+
skipRender: true
|
|
4755
|
+
});
|
|
4741
4756
|
if (this.isToolActive) {
|
|
4742
4757
|
this.updateImages();
|
|
4743
4758
|
}
|
|
@@ -4766,16 +4781,13 @@ var ImageTool = class {
|
|
|
4766
4781
|
});
|
|
4767
4782
|
this.updateConfig(next);
|
|
4768
4783
|
if (replacingSource) {
|
|
4769
|
-
this.focusedImageId = id;
|
|
4770
|
-
this.isImageSelectionActive = true;
|
|
4771
|
-
this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
|
|
4772
4784
|
this.debug("replace:image:begin", { id, replacingUrl });
|
|
4773
4785
|
this.purgeSourceSizeCacheForItem(base);
|
|
4774
4786
|
const loaded = await this.waitImageLoaded(id, true);
|
|
4775
4787
|
this.debug("replace:image:loaded", { id, loaded });
|
|
4776
4788
|
if (loaded) {
|
|
4777
4789
|
await this.refitImageToFrame(id);
|
|
4778
|
-
this.
|
|
4790
|
+
this.setImageFocus(id);
|
|
4779
4791
|
}
|
|
4780
4792
|
}
|
|
4781
4793
|
}
|
|
@@ -4819,29 +4831,9 @@ var ImageTool = class {
|
|
|
4819
4831
|
this.updateConfig(next);
|
|
4820
4832
|
this.workingItems = this.cloneItems(next);
|
|
4821
4833
|
this.hasWorkingChanges = false;
|
|
4822
|
-
this.isImageSelectionActive = true;
|
|
4823
|
-
this.focusedImageId = id;
|
|
4824
4834
|
this.updateImages();
|
|
4825
4835
|
this.emitWorkingChange(id);
|
|
4826
4836
|
}
|
|
4827
|
-
focusImageSelection(id) {
|
|
4828
|
-
if (!this.canvasService) return;
|
|
4829
|
-
const obj = this.getImageObject(id);
|
|
4830
|
-
if (!obj) return;
|
|
4831
|
-
this.isImageSelectionActive = true;
|
|
4832
|
-
this.focusedImageId = id;
|
|
4833
|
-
this.suppressSelectionClearUntil = Date.now() + 700;
|
|
4834
|
-
obj.set({
|
|
4835
|
-
selectable: true,
|
|
4836
|
-
evented: true,
|
|
4837
|
-
hasControls: true,
|
|
4838
|
-
hasBorders: true
|
|
4839
|
-
});
|
|
4840
|
-
this.canvasService.canvas.setActiveObject(obj);
|
|
4841
|
-
this.debug("focus:image", { id });
|
|
4842
|
-
this.canvasService.requestRenderAll();
|
|
4843
|
-
this.updateImages();
|
|
4844
|
-
}
|
|
4845
4837
|
async fitImageToArea(id, area) {
|
|
4846
4838
|
var _a, _b;
|
|
4847
4839
|
if (!this.canvasService) return;
|
|
@@ -4891,7 +4883,6 @@ var ImageTool = class {
|
|
|
4891
4883
|
if (!frame.width || !frame.height) {
|
|
4892
4884
|
return { ok: false, reason: "frame-not-ready" };
|
|
4893
4885
|
}
|
|
4894
|
-
const focusId = this.resolveReplaceTargetId(this.focusedImageId) || (this.workingItems.length === 1 ? this.workingItems[0].id : null);
|
|
4895
4886
|
const next = [];
|
|
4896
4887
|
for (const item of this.workingItems) {
|
|
4897
4888
|
const url = await this.exportCroppedImageByIds([item.id], {
|
|
@@ -4915,13 +4906,7 @@ var ImageTool = class {
|
|
|
4915
4906
|
this.hasWorkingChanges = false;
|
|
4916
4907
|
this.workingItems = this.cloneItems(next);
|
|
4917
4908
|
this.updateConfig(next);
|
|
4918
|
-
this.emitWorkingChange(
|
|
4919
|
-
if (focusId) {
|
|
4920
|
-
this.focusedImageId = focusId;
|
|
4921
|
-
this.isImageSelectionActive = true;
|
|
4922
|
-
this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
|
|
4923
|
-
this.focusImageSelection(focusId);
|
|
4924
|
-
}
|
|
4909
|
+
this.emitWorkingChange(this.focusedImageId);
|
|
4925
4910
|
return { ok: true };
|
|
4926
4911
|
}
|
|
4927
4912
|
async exportCroppedImageByIds(imageIds, options) {
|
package/package.json
CHANGED
package/src/image.ts
CHANGED
|
@@ -57,8 +57,7 @@ interface FrameVisualConfig {
|
|
|
57
57
|
|
|
58
58
|
interface UpsertImageOptions {
|
|
59
59
|
id?: string;
|
|
60
|
-
mode?: "
|
|
61
|
-
createIfMissing?: boolean;
|
|
60
|
+
mode?: "replace" | "add";
|
|
62
61
|
addOptions?: Partial<ImageItem>;
|
|
63
62
|
fitOnAdd?: boolean;
|
|
64
63
|
}
|
|
@@ -108,7 +107,6 @@ interface DetectFromFrameOptions {
|
|
|
108
107
|
|
|
109
108
|
const IMAGE_OBJECT_LAYER_ID = "image.user";
|
|
110
109
|
const IMAGE_OVERLAY_LAYER_ID = "image-overlay";
|
|
111
|
-
const IMAGE_REPLACE_GUARD_MS = 2500;
|
|
112
110
|
const IMAGE_DETECT_EXPAND_DEFAULT = 30;
|
|
113
111
|
const IMAGE_DETECT_SIMPLIFY_TOLERANCE_DEFAULT = 2;
|
|
114
112
|
const IMAGE_DETECT_MULTIPLIER_DEFAULT = 2;
|
|
@@ -131,7 +129,6 @@ export class ImageTool implements Extension {
|
|
|
131
129
|
private isToolActive = false;
|
|
132
130
|
private isImageSelectionActive = false;
|
|
133
131
|
private focusedImageId: string | null = null;
|
|
134
|
-
private suppressSelectionClearUntil = 0;
|
|
135
132
|
private renderSeq = 0;
|
|
136
133
|
private dirtyTrackerDisposable?: { dispose(): void };
|
|
137
134
|
|
|
@@ -218,13 +215,10 @@ export class ImageTool implements Extension {
|
|
|
218
215
|
const before = this.isToolActive;
|
|
219
216
|
this.syncToolActiveFromWorkbench(event.id);
|
|
220
217
|
if (!this.isToolActive) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
this.isImageSelectionActive = false;
|
|
226
|
-
this.focusedImageId = null;
|
|
227
|
-
}
|
|
218
|
+
this.setImageFocus(null, {
|
|
219
|
+
syncCanvasSelection: true,
|
|
220
|
+
skipRender: true,
|
|
221
|
+
});
|
|
228
222
|
}
|
|
229
223
|
this.debug("tool:activated", {
|
|
230
224
|
id: event.id,
|
|
@@ -233,7 +227,6 @@ export class ImageTool implements Extension {
|
|
|
233
227
|
before,
|
|
234
228
|
isToolActive: this.isToolActive,
|
|
235
229
|
focusedImageId: this.focusedImageId,
|
|
236
|
-
suppressSelectionClearUntil: this.suppressSelectionClearUntil,
|
|
237
230
|
});
|
|
238
231
|
if (!this.isToolActive && this.isDebugEnabled()) {
|
|
239
232
|
console.trace("[ImageTool] tool deactivated trace");
|
|
@@ -271,16 +264,10 @@ export class ImageTool implements Extension {
|
|
|
271
264
|
};
|
|
272
265
|
|
|
273
266
|
private onSelectionCleared = () => {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
focusedImageId: this.focusedImageId,
|
|
279
|
-
});
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
this.isImageSelectionActive = false;
|
|
283
|
-
this.focusedImageId = null;
|
|
267
|
+
this.setImageFocus(null, {
|
|
268
|
+
syncCanvasSelection: false,
|
|
269
|
+
skipRender: true,
|
|
270
|
+
});
|
|
284
271
|
this.debug("selection:cleared applied");
|
|
285
272
|
this.updateImages();
|
|
286
273
|
};
|
|
@@ -353,7 +340,7 @@ export class ImageTool implements Extension {
|
|
|
353
340
|
id: "image.frame.strokeColor",
|
|
354
341
|
type: "color",
|
|
355
342
|
label: "Image Frame Stroke Color",
|
|
356
|
-
default: "#
|
|
343
|
+
default: "#808080",
|
|
357
344
|
},
|
|
358
345
|
{
|
|
359
346
|
id: "image.frame.strokeWidth",
|
|
@@ -369,7 +356,7 @@ export class ImageTool implements Extension {
|
|
|
369
356
|
type: "select",
|
|
370
357
|
label: "Image Frame Stroke Style",
|
|
371
358
|
options: ["solid", "dashed", "hidden"],
|
|
372
|
-
default: "
|
|
359
|
+
default: "dashed",
|
|
373
360
|
},
|
|
374
361
|
{
|
|
375
362
|
id: "image.frame.dashLength",
|
|
@@ -474,6 +461,16 @@ export class ImageTool implements Extension {
|
|
|
474
461
|
await this.fitImageToDefaultArea(id);
|
|
475
462
|
},
|
|
476
463
|
},
|
|
464
|
+
{
|
|
465
|
+
command: "focusImage",
|
|
466
|
+
title: "Focus Image",
|
|
467
|
+
handler: (
|
|
468
|
+
id: string | null,
|
|
469
|
+
options: { syncCanvasSelection?: boolean } = {},
|
|
470
|
+
) => {
|
|
471
|
+
return this.setImageFocus(id, options);
|
|
472
|
+
},
|
|
473
|
+
},
|
|
477
474
|
{
|
|
478
475
|
command: "removeImage",
|
|
479
476
|
title: "Remove Image",
|
|
@@ -483,8 +480,10 @@ export class ImageTool implements Extension {
|
|
|
483
480
|
if (next.length !== this.items.length) {
|
|
484
481
|
this.purgeSourceSizeCacheForItem(removed);
|
|
485
482
|
if (this.focusedImageId === id) {
|
|
486
|
-
this.
|
|
487
|
-
|
|
483
|
+
this.setImageFocus(null, {
|
|
484
|
+
syncCanvasSelection: true,
|
|
485
|
+
skipRender: true,
|
|
486
|
+
});
|
|
488
487
|
}
|
|
489
488
|
this.updateConfig(next);
|
|
490
489
|
}
|
|
@@ -506,8 +505,10 @@ export class ImageTool implements Extension {
|
|
|
506
505
|
title: "Clear Images",
|
|
507
506
|
handler: () => {
|
|
508
507
|
this.sourceSizeBySrc.clear();
|
|
509
|
-
this.
|
|
510
|
-
|
|
508
|
+
this.setImageFocus(null, {
|
|
509
|
+
syncCanvasSelection: true,
|
|
510
|
+
skipRender: true,
|
|
511
|
+
});
|
|
511
512
|
this.updateConfig([]);
|
|
512
513
|
},
|
|
513
514
|
},
|
|
@@ -584,29 +585,50 @@ export class ImageTool implements Extension {
|
|
|
584
585
|
return Math.random().toString(36).substring(2, 9);
|
|
585
586
|
}
|
|
586
587
|
|
|
587
|
-
private
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
) {
|
|
593
|
-
return active.data.id;
|
|
594
|
-
}
|
|
595
|
-
return null;
|
|
588
|
+
private hasImageItem(id: string): boolean {
|
|
589
|
+
return (
|
|
590
|
+
this.items.some((item) => item.id === id) ||
|
|
591
|
+
this.workingItems.some((item) => item.id === id)
|
|
592
|
+
);
|
|
596
593
|
}
|
|
597
594
|
|
|
598
|
-
private
|
|
599
|
-
|
|
600
|
-
|
|
595
|
+
private setImageFocus(
|
|
596
|
+
id: string | null,
|
|
597
|
+
options: { syncCanvasSelection?: boolean; skipRender?: boolean } = {},
|
|
598
|
+
) {
|
|
599
|
+
const syncCanvasSelection = options.syncCanvasSelection !== false;
|
|
601
600
|
|
|
602
|
-
if (
|
|
603
|
-
|
|
601
|
+
if (id && !this.hasImageItem(id)) {
|
|
602
|
+
return { ok: false, reason: "image-not-found" as const };
|
|
603
|
+
}
|
|
604
604
|
|
|
605
|
-
|
|
606
|
-
|
|
605
|
+
this.focusedImageId = id;
|
|
606
|
+
this.isImageSelectionActive = !!id;
|
|
607
|
+
|
|
608
|
+
if (syncCanvasSelection && this.canvasService) {
|
|
609
|
+
const canvas = this.canvasService.canvas;
|
|
610
|
+
if (!id) {
|
|
611
|
+
canvas.discardActiveObject();
|
|
612
|
+
} else {
|
|
613
|
+
const obj = this.getImageObject(id);
|
|
614
|
+
if (obj) {
|
|
615
|
+
obj.set({
|
|
616
|
+
selectable: true,
|
|
617
|
+
evented: true,
|
|
618
|
+
hasControls: true,
|
|
619
|
+
hasBorders: true,
|
|
620
|
+
});
|
|
621
|
+
canvas.setActiveObject(obj);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
this.canvasService.requestRenderAll();
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (!options.skipRender) {
|
|
628
|
+
this.updateImages();
|
|
629
|
+
}
|
|
607
630
|
|
|
608
|
-
|
|
609
|
-
return null;
|
|
631
|
+
return { ok: true, id };
|
|
610
632
|
}
|
|
611
633
|
|
|
612
634
|
private async addImageEntry(
|
|
@@ -622,9 +644,6 @@ export class ImageTool implements Extension {
|
|
|
622
644
|
...options,
|
|
623
645
|
} as ImageItem);
|
|
624
646
|
|
|
625
|
-
this.focusedImageId = id;
|
|
626
|
-
this.isImageSelectionActive = true;
|
|
627
|
-
this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
|
|
628
647
|
const sessionDirtyBeforeAdd = this.isToolActive && this.hasWorkingChanges;
|
|
629
648
|
const waitLoaded = this.waitImageLoaded(id, true);
|
|
630
649
|
this.updateConfig([...this.items, newItem]);
|
|
@@ -634,7 +653,7 @@ export class ImageTool implements Extension {
|
|
|
634
653
|
await this.fitImageToDefaultArea(id);
|
|
635
654
|
}
|
|
636
655
|
if (loaded) {
|
|
637
|
-
this.
|
|
656
|
+
this.setImageFocus(id);
|
|
638
657
|
}
|
|
639
658
|
return id;
|
|
640
659
|
}
|
|
@@ -643,23 +662,20 @@ export class ImageTool implements Extension {
|
|
|
643
662
|
url: string,
|
|
644
663
|
options: UpsertImageOptions = {},
|
|
645
664
|
): Promise<{ id: string; mode: "replace" | "add" }> {
|
|
646
|
-
const mode = options.mode || "
|
|
665
|
+
const mode = options.mode || (options.id ? "replace" : "add");
|
|
647
666
|
const fitOnAdd = options.fitOnAdd !== false;
|
|
648
|
-
if (mode === "
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
667
|
+
if (mode === "replace") {
|
|
668
|
+
if (!options.id) {
|
|
669
|
+
throw new Error("replace-target-id-required");
|
|
670
|
+
}
|
|
671
|
+
const targetId = options.id;
|
|
672
|
+
if (!this.hasImageItem(targetId)) {
|
|
673
|
+
throw new Error("replace-target-not-found");
|
|
674
|
+
}
|
|
655
675
|
await this.updateImageInConfig(targetId, { url });
|
|
656
676
|
return { id: targetId, mode: "replace" };
|
|
657
677
|
}
|
|
658
678
|
|
|
659
|
-
if (mode === "replace" || options.createIfMissing === false) {
|
|
660
|
-
throw new Error("replace-target-not-found");
|
|
661
|
-
}
|
|
662
|
-
|
|
663
679
|
const id = await this.addImageEntry(url, options.addOptions, fitOnAdd);
|
|
664
680
|
return { id, mode: "add" };
|
|
665
681
|
}
|
|
@@ -870,12 +886,12 @@ export class ImageTool implements Extension {
|
|
|
870
886
|
private getFrameVisualConfig(): FrameVisualConfig {
|
|
871
887
|
const strokeStyleRaw = (this.getConfig<string>(
|
|
872
888
|
"image.frame.strokeStyle",
|
|
873
|
-
"
|
|
874
|
-
) || "
|
|
889
|
+
"dashed",
|
|
890
|
+
) || "dashed") as string;
|
|
875
891
|
const strokeStyle: "solid" | "dashed" | "hidden" =
|
|
876
892
|
strokeStyleRaw === "dashed" || strokeStyleRaw === "hidden"
|
|
877
893
|
? strokeStyleRaw
|
|
878
|
-
: "
|
|
894
|
+
: "dashed";
|
|
879
895
|
|
|
880
896
|
const strokeWidth = Number(
|
|
881
897
|
this.getConfig<number>("image.frame.strokeWidth", 2) ?? 2,
|
|
@@ -886,8 +902,8 @@ export class ImageTool implements Extension {
|
|
|
886
902
|
|
|
887
903
|
return {
|
|
888
904
|
strokeColor:
|
|
889
|
-
this.getConfig<string>("image.frame.strokeColor", "#
|
|
890
|
-
"#
|
|
905
|
+
this.getConfig<string>("image.frame.strokeColor", "#808080") ||
|
|
906
|
+
"#808080",
|
|
891
907
|
strokeWidth: Number.isFinite(strokeWidth) ? Math.max(0, strokeWidth) : 2,
|
|
892
908
|
strokeStyle,
|
|
893
909
|
dashLength: Number.isFinite(dashLength) ? Math.max(1, dashLength) : 8,
|
|
@@ -1199,8 +1215,10 @@ export class ImageTool implements Extension {
|
|
|
1199
1215
|
const frame = this.getFrameRect();
|
|
1200
1216
|
const desiredIds = new Set(renderItems.map((item) => item.id));
|
|
1201
1217
|
if (this.focusedImageId && !desiredIds.has(this.focusedImageId)) {
|
|
1202
|
-
this.
|
|
1203
|
-
|
|
1218
|
+
this.setImageFocus(null, {
|
|
1219
|
+
syncCanvasSelection: false,
|
|
1220
|
+
skipRender: true,
|
|
1221
|
+
});
|
|
1204
1222
|
}
|
|
1205
1223
|
|
|
1206
1224
|
this.getImageObjects().forEach((obj: any) => {
|
|
@@ -1281,8 +1299,10 @@ export class ImageTool implements Extension {
|
|
|
1281
1299
|
next[index] = this.normalizeItem({ ...next[index], ...updates });
|
|
1282
1300
|
this.workingItems = next;
|
|
1283
1301
|
this.hasWorkingChanges = true;
|
|
1284
|
-
this.
|
|
1285
|
-
|
|
1302
|
+
this.setImageFocus(id, {
|
|
1303
|
+
syncCanvasSelection: false,
|
|
1304
|
+
skipRender: true,
|
|
1305
|
+
});
|
|
1286
1306
|
if (this.isToolActive) {
|
|
1287
1307
|
this.updateImages();
|
|
1288
1308
|
}
|
|
@@ -1318,16 +1338,13 @@ export class ImageTool implements Extension {
|
|
|
1318
1338
|
this.updateConfig(next);
|
|
1319
1339
|
|
|
1320
1340
|
if (replacingSource) {
|
|
1321
|
-
this.focusedImageId = id;
|
|
1322
|
-
this.isImageSelectionActive = true;
|
|
1323
|
-
this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
|
|
1324
1341
|
this.debug("replace:image:begin", { id, replacingUrl });
|
|
1325
1342
|
this.purgeSourceSizeCacheForItem(base);
|
|
1326
1343
|
const loaded = await this.waitImageLoaded(id, true);
|
|
1327
1344
|
this.debug("replace:image:loaded", { id, loaded });
|
|
1328
1345
|
if (loaded) {
|
|
1329
1346
|
await this.refitImageToFrame(id);
|
|
1330
|
-
this.
|
|
1347
|
+
this.setImageFocus(id);
|
|
1331
1348
|
}
|
|
1332
1349
|
}
|
|
1333
1350
|
}
|
|
@@ -1380,32 +1397,10 @@ export class ImageTool implements Extension {
|
|
|
1380
1397
|
this.updateConfig(next);
|
|
1381
1398
|
this.workingItems = this.cloneItems(next);
|
|
1382
1399
|
this.hasWorkingChanges = false;
|
|
1383
|
-
this.isImageSelectionActive = true;
|
|
1384
|
-
this.focusedImageId = id;
|
|
1385
1400
|
this.updateImages();
|
|
1386
1401
|
this.emitWorkingChange(id);
|
|
1387
1402
|
}
|
|
1388
1403
|
|
|
1389
|
-
private focusImageSelection(id: string) {
|
|
1390
|
-
if (!this.canvasService) return;
|
|
1391
|
-
const obj = this.getImageObject(id);
|
|
1392
|
-
if (!obj) return;
|
|
1393
|
-
|
|
1394
|
-
this.isImageSelectionActive = true;
|
|
1395
|
-
this.focusedImageId = id;
|
|
1396
|
-
this.suppressSelectionClearUntil = Date.now() + 700;
|
|
1397
|
-
obj.set({
|
|
1398
|
-
selectable: true,
|
|
1399
|
-
evented: true,
|
|
1400
|
-
hasControls: true,
|
|
1401
|
-
hasBorders: true,
|
|
1402
|
-
});
|
|
1403
|
-
this.canvasService.canvas.setActiveObject(obj);
|
|
1404
|
-
this.debug("focus:image", { id });
|
|
1405
|
-
this.canvasService.requestRenderAll();
|
|
1406
|
-
this.updateImages();
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
1404
|
private async fitImageToArea(
|
|
1410
1405
|
id: string,
|
|
1411
1406
|
area: { width: number; height: number; left?: number; top?: number },
|
|
@@ -1473,10 +1468,6 @@ export class ImageTool implements Extension {
|
|
|
1473
1468
|
return { ok: false, reason: "frame-not-ready" };
|
|
1474
1469
|
}
|
|
1475
1470
|
|
|
1476
|
-
const focusId =
|
|
1477
|
-
this.resolveReplaceTargetId(this.focusedImageId) ||
|
|
1478
|
-
(this.workingItems.length === 1 ? this.workingItems[0].id : null);
|
|
1479
|
-
|
|
1480
1471
|
const next: ImageItem[] = [];
|
|
1481
1472
|
for (const item of this.workingItems) {
|
|
1482
1473
|
const url = await this.exportCroppedImageByIds([item.id], {
|
|
@@ -1502,13 +1493,7 @@ export class ImageTool implements Extension {
|
|
|
1502
1493
|
this.hasWorkingChanges = false;
|
|
1503
1494
|
this.workingItems = this.cloneItems(next);
|
|
1504
1495
|
this.updateConfig(next);
|
|
1505
|
-
this.emitWorkingChange(
|
|
1506
|
-
if (focusId) {
|
|
1507
|
-
this.focusedImageId = focusId;
|
|
1508
|
-
this.isImageSelectionActive = true;
|
|
1509
|
-
this.suppressSelectionClearUntil = Date.now() + IMAGE_REPLACE_GUARD_MS;
|
|
1510
|
-
this.focusImageSelection(focusId);
|
|
1511
|
-
}
|
|
1496
|
+
this.emitWorkingChange(this.focusedImageId);
|
|
1512
1497
|
return { ok: true };
|
|
1513
1498
|
}
|
|
1514
1499
|
|