@pooder/kit 6.2.2 → 6.3.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.
@@ -39,6 +39,11 @@ import {
39
39
  } from "../../shared/constants/layers";
40
40
  import { createImageCommands } from "./commands";
41
41
  import { createImageConfigurations } from "./config";
42
+ import {
43
+ computeImageOperationUpdates,
44
+ resolveImageOperationArea,
45
+ type ImageOperation,
46
+ } from "./imageOperations";
42
47
  import { buildImageSessionOverlaySpecs } from "./sessionOverlay";
43
48
 
44
49
  export interface ImageItem {
@@ -53,6 +58,25 @@ export interface ImageItem {
53
58
  committedUrl?: string;
54
59
  }
55
60
 
61
+ export interface ImageTransformUpdates {
62
+ scale?: number;
63
+ angle?: number;
64
+ left?: number;
65
+ top?: number;
66
+ opacity?: number;
67
+ }
68
+
69
+ export interface ImageViewState {
70
+ items: ImageItem[];
71
+ hasAnyImage: boolean;
72
+ focusedId: string | null;
73
+ focusedItem: ImageItem | null;
74
+ isToolActive: boolean;
75
+ isImageSelectionActive: boolean;
76
+ hasWorkingChanges: boolean;
77
+ source: "working" | "committed";
78
+ }
79
+
56
80
  interface RenderImageState {
57
81
  src: string;
58
82
  left: number;
@@ -92,14 +116,7 @@ interface UpsertImageOptions {
92
116
  id?: string;
93
117
  mode?: "replace" | "add";
94
118
  addOptions?: Partial<ImageItem>;
95
- fitOnAdd?: boolean;
96
- }
97
-
98
- interface DielineFitArea {
99
- width: number;
100
- height: number;
101
- left: number;
102
- top: number;
119
+ operation?: ImageOperation;
103
120
  }
104
121
 
105
122
  interface UpdateImageOptions {
@@ -370,6 +387,7 @@ export class ImageTool implements Extension {
370
387
  this.clearRenderedImages();
371
388
  this.renderProducerDisposable?.dispose();
372
389
  this.renderProducerDisposable = undefined;
390
+ this.emitImageStateChange();
373
391
  if (this.canvasService) {
374
392
  void this.canvasService.flushRenderFromProducers();
375
393
  this.canvasService = undefined;
@@ -932,9 +950,9 @@ export class ImageTool implements Extension {
932
950
  name: "Image",
933
951
  interaction: "session",
934
952
  commands: {
935
- begin: "resetWorkingImages",
953
+ begin: "imageSessionReset",
936
954
  commit: "completeImages",
937
- rollback: "resetWorkingImages",
955
+ rollback: "imageSessionReset",
938
956
  },
939
957
  session: {
940
958
  autoBegin: true,
@@ -979,6 +997,34 @@ export class ImageTool implements Extension {
979
997
  return this.normalizeItems((items || []).map((i) => ({ ...i })));
980
998
  }
981
999
 
1000
+ private getViewItems(): ImageItem[] {
1001
+ return this.isToolActive ? this.workingItems : this.items;
1002
+ }
1003
+
1004
+ private getImageViewState(): ImageViewState {
1005
+ this.syncToolActiveFromWorkbench();
1006
+ const items = this.cloneItems(this.getViewItems());
1007
+ const focusedItem =
1008
+ this.focusedImageId == null
1009
+ ? null
1010
+ : items.find((item) => item.id === this.focusedImageId) || null;
1011
+
1012
+ return {
1013
+ items,
1014
+ hasAnyImage: items.length > 0,
1015
+ focusedId: this.focusedImageId,
1016
+ focusedItem,
1017
+ isToolActive: this.isToolActive,
1018
+ isImageSelectionActive: this.isImageSelectionActive,
1019
+ hasWorkingChanges: this.hasWorkingChanges,
1020
+ source: this.isToolActive ? "working" : "committed",
1021
+ };
1022
+ }
1023
+
1024
+ private emitImageStateChange() {
1025
+ this.context?.eventBus.emit("image:state:change", this.getImageViewState());
1026
+ }
1027
+
982
1028
  private emitWorkingChange(changedId: string | null = null) {
983
1029
  this.context?.eventBus.emit("image:working:change", {
984
1030
  changedId,
@@ -1026,6 +1072,8 @@ export class ImageTool implements Extension {
1026
1072
 
1027
1073
  if (!options.skipRender) {
1028
1074
  this.updateImages();
1075
+ } else {
1076
+ this.emitImageStateChange();
1029
1077
  }
1030
1078
 
1031
1079
  return { ok: true, id };
@@ -1034,8 +1082,9 @@ export class ImageTool implements Extension {
1034
1082
  private async addImageEntry(
1035
1083
  url: string,
1036
1084
  options?: Partial<ImageItem>,
1037
- fitOnAdd = true,
1085
+ operation?: ImageOperation,
1038
1086
  ): Promise<string> {
1087
+ this.syncToolActiveFromWorkbench();
1039
1088
  const id = this.generateId();
1040
1089
  const newItem = this.normalizeItem({
1041
1090
  id,
@@ -1044,13 +1093,20 @@ export class ImageTool implements Extension {
1044
1093
  ...options,
1045
1094
  } as ImageItem);
1046
1095
 
1047
- const sessionDirtyBeforeAdd = this.isToolActive && this.hasWorkingChanges;
1048
1096
  const waitLoaded = this.waitImageLoaded(id, true);
1049
- this.updateConfig([...this.items, newItem]);
1050
- this.addItemToWorkingSessionIfNeeded(newItem, sessionDirtyBeforeAdd);
1097
+ if (this.isToolActive) {
1098
+ this.workingItems = this.cloneItems([...this.workingItems, newItem]);
1099
+ this.hasWorkingChanges = true;
1100
+ this.updateImages();
1101
+ this.emitWorkingChange(id);
1102
+ } else {
1103
+ this.updateConfig([...this.items, newItem]);
1104
+ }
1051
1105
  const loaded = await waitLoaded;
1052
- if (loaded && fitOnAdd) {
1053
- await this.fitImageToDefaultArea(id);
1106
+ if (loaded && operation) {
1107
+ await this.applyImageOperation(id, operation, {
1108
+ target: this.isToolActive ? "working" : "config",
1109
+ });
1054
1110
  }
1055
1111
  if (loaded) {
1056
1112
  this.setImageFocus(id);
@@ -1062,8 +1118,8 @@ export class ImageTool implements Extension {
1062
1118
  url: string,
1063
1119
  options: UpsertImageOptions = {},
1064
1120
  ): Promise<{ id: string; mode: "replace" | "add" }> {
1121
+ this.syncToolActiveFromWorkbench();
1065
1122
  const mode = options.mode || (options.id ? "replace" : "add");
1066
- const fitOnAdd = options.fitOnAdd !== false;
1067
1123
  if (mode === "replace") {
1068
1124
  if (!options.id) {
1069
1125
  throw new Error("replace-target-id-required");
@@ -1072,25 +1128,35 @@ export class ImageTool implements Extension {
1072
1128
  if (!this.hasImageItem(targetId)) {
1073
1129
  throw new Error("replace-target-not-found");
1074
1130
  }
1075
- await this.updateImageInConfig(targetId, { url });
1131
+ if (this.isToolActive) {
1132
+ const current =
1133
+ this.workingItems.find((item) => item.id === targetId) ||
1134
+ this.items.find((item) => item.id === targetId);
1135
+ this.purgeSourceSizeCacheForItem(current);
1136
+ this.updateImageInWorking(targetId, {
1137
+ url,
1138
+ sourceUrl: url,
1139
+ committedUrl: undefined,
1140
+ });
1141
+ } else {
1142
+ await this.updateImageInConfig(targetId, { url });
1143
+ }
1144
+ const loaded = await this.waitImageLoaded(targetId, true);
1145
+ if (loaded && options.operation) {
1146
+ await this.applyImageOperation(targetId, options.operation, {
1147
+ target: this.isToolActive ? "working" : "config",
1148
+ });
1149
+ }
1150
+ if (loaded) {
1151
+ this.setImageFocus(targetId);
1152
+ }
1076
1153
  return { id: targetId, mode: "replace" };
1077
1154
  }
1078
1155
 
1079
- const id = await this.addImageEntry(url, options.addOptions, fitOnAdd);
1156
+ const id = await this.addImageEntry(url, options.addOptions, options.operation);
1080
1157
  return { id, mode: "add" };
1081
1158
  }
1082
1159
 
1083
- private addItemToWorkingSessionIfNeeded(
1084
- item: ImageItem,
1085
- sessionDirtyBeforeAdd: boolean,
1086
- ) {
1087
- if (!sessionDirtyBeforeAdd || !this.isToolActive) return;
1088
- if (this.workingItems.some((existing) => existing.id === item.id)) return;
1089
- this.workingItems = this.cloneItems([...this.workingItems, item]);
1090
- this.updateImages();
1091
- this.emitWorkingChange(item.id);
1092
- }
1093
-
1094
1160
  private async updateImage(
1095
1161
  id: string,
1096
1162
  updates: Partial<ImageItem>,
@@ -1165,38 +1231,6 @@ export class ImageTool implements Extension {
1165
1231
  return this.canvasService.toScreenRect(frame || this.getFrameRect());
1166
1232
  }
1167
1233
 
1168
- private async resolveDefaultFitArea(): Promise<DielineFitArea | null> {
1169
- if (!this.canvasService) return null;
1170
- const frame = this.getFrameRect();
1171
- if (frame.width <= 0 || frame.height <= 0) return null;
1172
- return {
1173
- width: Math.max(1, frame.width),
1174
- height: Math.max(1, frame.height),
1175
- left: frame.left + frame.width / 2,
1176
- top: frame.top + frame.height / 2,
1177
- };
1178
- }
1179
-
1180
- private async fitImageToDefaultArea(id: string) {
1181
- if (!this.canvasService) return;
1182
- const area = await this.resolveDefaultFitArea();
1183
-
1184
- if (area) {
1185
- await this.fitImageToArea(id, area);
1186
- return;
1187
- }
1188
-
1189
- const viewport = this.canvasService.getSceneViewportRect();
1190
- const canvasW = Math.max(1, viewport.width || 0);
1191
- const canvasH = Math.max(1, viewport.height || 0);
1192
- await this.fitImageToArea(id, {
1193
- width: canvasW,
1194
- height: canvasH,
1195
- left: viewport.left + canvasW / 2,
1196
- top: viewport.top + canvasH / 2,
1197
- });
1198
- }
1199
-
1200
1234
  private getImageObjects(): any[] {
1201
1235
  if (!this.canvasService) return [];
1202
1236
  return this.canvasService.canvas.getObjects().filter((obj: any) => {
@@ -1587,6 +1621,7 @@ export class ImageTool implements Extension {
1587
1621
  isImageSelectionActive: this.isImageSelectionActive,
1588
1622
  focusedImageId: this.focusedImageId,
1589
1623
  });
1624
+ this.emitImageStateChange();
1590
1625
  this.canvasService.requestRenderAll();
1591
1626
  }
1592
1627
 
@@ -1594,6 +1629,40 @@ export class ImageTool implements Extension {
1594
1629
  return Math.max(-1, Math.min(2, value));
1595
1630
  }
1596
1631
 
1632
+ private async setImageTransform(
1633
+ id: string,
1634
+ updates: ImageTransformUpdates,
1635
+ options: UpdateImageOptions = {},
1636
+ ) {
1637
+ const next: Partial<ImageItem> = {};
1638
+
1639
+ if (Number.isFinite(updates.scale as number)) {
1640
+ next.scale = Math.max(0.05, Number(updates.scale));
1641
+ }
1642
+ if (Number.isFinite(updates.angle as number)) {
1643
+ next.angle = Number(updates.angle);
1644
+ }
1645
+ if (Number.isFinite(updates.left as number)) {
1646
+ next.left = this.clampNormalized(Number(updates.left));
1647
+ }
1648
+ if (Number.isFinite(updates.top as number)) {
1649
+ next.top = this.clampNormalized(Number(updates.top));
1650
+ }
1651
+ if (Number.isFinite(updates.opacity as number)) {
1652
+ next.opacity = Math.max(0, Math.min(1, Number(updates.opacity)));
1653
+ }
1654
+
1655
+ if (!Object.keys(next).length) return;
1656
+ await this.updateImage(id, next, options);
1657
+ }
1658
+
1659
+ private resetImageSession() {
1660
+ this.workingItems = this.cloneItems(this.items);
1661
+ this.hasWorkingChanges = false;
1662
+ this.updateImages();
1663
+ this.emitWorkingChange();
1664
+ }
1665
+
1597
1666
  private onObjectModified = (e: any) => {
1598
1667
  if (!this.isToolActive) return;
1599
1668
  const target = e?.target;
@@ -1669,10 +1738,6 @@ export class ImageTool implements Extension {
1669
1738
  url: replacingUrl,
1670
1739
  sourceUrl: replacingUrl,
1671
1740
  committedUrl: undefined,
1672
- scale: updates.scale ?? 1,
1673
- angle: updates.angle ?? 0,
1674
- left: updates.left ?? 0.5,
1675
- top: updates.top ?? 0.5,
1676
1741
  }
1677
1742
  : {}),
1678
1743
  });
@@ -1680,14 +1745,7 @@ export class ImageTool implements Extension {
1680
1745
  this.updateConfig(next);
1681
1746
 
1682
1747
  if (replacingSource) {
1683
- this.debug("replace:image:begin", { id, replacingUrl });
1684
1748
  this.purgeSourceSizeCacheForItem(base);
1685
- const loaded = await this.waitImageLoaded(id, true);
1686
- this.debug("replace:image:loaded", { id, loaded });
1687
- if (loaded) {
1688
- await this.refitImageToFrame(id);
1689
- this.setImageFocus(id);
1690
- }
1691
1749
  }
1692
1750
  }
1693
1751
 
@@ -1709,93 +1767,62 @@ export class ImageTool implements Extension {
1709
1767
  });
1710
1768
  }
1711
1769
 
1712
- private async refitImageToFrame(id: string) {
1770
+ private async resolveImageSourceSize(
1771
+ id: string,
1772
+ src: string,
1773
+ ): Promise<SourceSize | null> {
1713
1774
  const obj = this.getImageObject(id);
1714
- if (!obj || !this.canvasService) return;
1715
- const current = this.items.find((item) => item.id === id);
1716
- if (!current) return;
1717
- const render = this.resolveRenderImageState(current);
1718
-
1719
- this.rememberSourceSize(render.src, obj);
1720
- const source = this.getSourceSize(render.src, obj);
1721
- const frame = this.getFrameRect();
1722
- const coverScale = this.getCoverScale(frame, source);
1723
-
1724
- const currentScale = this.toSceneObjectScale(obj.scaleX || 1);
1725
- const zoom = Math.max(0.05, currentScale / coverScale);
1726
-
1727
- const updated: Partial<ImageItem> = {
1728
- scale: Number.isFinite(zoom) ? zoom : 1,
1729
- angle: 0,
1730
- left: 0.5,
1731
- top: 0.5,
1732
- };
1733
-
1734
- const index = this.items.findIndex((item) => item.id === id);
1735
- if (index < 0) return;
1775
+ if (obj) {
1776
+ this.rememberSourceSize(src, obj);
1777
+ }
1778
+ const ensured = await this.ensureSourceSize(src);
1779
+ if (ensured) return ensured;
1780
+ if (!obj) return null;
1736
1781
 
1737
- const next = [...this.items];
1738
- next[index] = this.normalizeItem({ ...next[index], ...updated });
1739
- this.updateConfig(next);
1740
- this.workingItems = this.cloneItems(next);
1741
- this.hasWorkingChanges = false;
1742
- this.updateImages();
1743
- this.emitWorkingChange(id);
1782
+ const width = Number(obj?.width || 0);
1783
+ const height = Number(obj?.height || 0);
1784
+ if (width <= 0 || height <= 0) return null;
1785
+ return { width, height };
1744
1786
  }
1745
1787
 
1746
- private async fitImageToArea(
1788
+ private async applyImageOperation(
1747
1789
  id: string,
1748
- area: { width: number; height: number; left?: number; top?: number },
1790
+ operation: ImageOperation,
1791
+ options: UpdateImageOptions = {},
1749
1792
  ) {
1750
1793
  if (!this.canvasService) return;
1751
1794
 
1752
- const loaded = await this.waitImageLoaded(id, false);
1753
- if (!loaded) return;
1754
-
1755
- const obj = this.getImageObject(id);
1756
- if (!obj) return;
1757
- const renderItems = this.isToolActive ? this.workingItems : this.items;
1795
+ this.syncToolActiveFromWorkbench();
1796
+ const target = options.target || "auto";
1797
+ const renderItems =
1798
+ target === "working" || (target === "auto" && this.isToolActive)
1799
+ ? this.workingItems
1800
+ : this.items;
1758
1801
  const current = renderItems.find((item) => item.id === id);
1759
1802
  if (!current) return;
1803
+
1760
1804
  const render = this.resolveRenderImageState(current);
1805
+ const source = await this.resolveImageSourceSize(id, render.src);
1806
+ if (!source) return;
1761
1807
 
1762
- this.rememberSourceSize(render.src, obj);
1763
- const source = this.getSourceSize(render.src, obj);
1764
1808
  const frame = this.getFrameRect();
1765
- const baseCover = this.getCoverScale(frame, source);
1766
-
1767
- const desiredScale = Math.max(
1768
- Math.max(1, area.width) / Math.max(1, source.width),
1769
- Math.max(1, area.height) / Math.max(1, source.height),
1770
- );
1771
-
1772
1809
  const viewport = this.canvasService.getSceneViewportRect();
1773
- const canvasW = viewport.width || 1;
1774
- const canvasH = viewport.height || 1;
1775
-
1776
- const areaLeftInput = area.left ?? 0.5;
1777
- const areaTopInput = area.top ?? 0.5;
1778
-
1779
- const areaLeftPx =
1780
- areaLeftInput <= 1.5
1781
- ? viewport.left + areaLeftInput * canvasW
1782
- : areaLeftInput;
1783
- const areaTopPx =
1784
- areaTopInput <= 1.5
1785
- ? viewport.top + areaTopInput * canvasH
1786
- : areaTopInput;
1787
-
1788
- const updates: Partial<ImageItem> = {
1789
- scale: Math.max(0.05, desiredScale / baseCover),
1790
- left: this.clampNormalized(
1791
- (areaLeftPx - frame.left) / Math.max(1, frame.width),
1792
- ),
1793
- top: this.clampNormalized(
1794
- (areaTopPx - frame.top) / Math.max(1, frame.height),
1795
- ),
1796
- };
1810
+ const area =
1811
+ operation.type === "resetTransform"
1812
+ ? resolveImageOperationArea({ frame, viewport })
1813
+ : resolveImageOperationArea({
1814
+ frame,
1815
+ viewport,
1816
+ area: operation.area,
1817
+ });
1818
+ const updates = computeImageOperationUpdates({
1819
+ frame,
1820
+ source,
1821
+ operation,
1822
+ area,
1823
+ });
1797
1824
 
1798
- if (this.isToolActive) {
1825
+ if (target === "working" || (target === "auto" && this.isToolActive)) {
1799
1826
  this.updateImageInWorking(id, updates);
1800
1827
  return;
1801
1828
  }
@@ -1,4 +1,5 @@
1
1
  import type { CommandContribution } from "@pooder/core";
2
+ import type { ImageOperation } from "./imageOperations";
2
3
 
3
4
  export function createImageCommands(tool: any): CommandContribution[] {
4
5
  return [
@@ -23,30 +24,43 @@ export function createImageCommands(tool: any): CommandContribution[] {
23
24
  },
24
25
  },
25
26
  {
26
- command: "getWorkingImages",
27
- id: "getWorkingImages",
28
- title: "Get Working Images",
27
+ command: "applyImageOperation",
28
+ id: "applyImageOperation",
29
+ title: "Apply Image Operation",
30
+ handler: async (
31
+ id: string,
32
+ operation: ImageOperation,
33
+ options: Record<string, any> = {},
34
+ ) => {
35
+ await tool.applyImageOperation(id, operation, options);
36
+ },
37
+ },
38
+ {
39
+ command: "getImageViewState",
40
+ id: "getImageViewState",
41
+ title: "Get Image View State",
29
42
  handler: () => {
30
- return tool.cloneItems(tool.workingItems);
43
+ return tool.getImageViewState();
31
44
  },
32
45
  },
33
46
  {
34
- command: "setWorkingImage",
35
- id: "setWorkingImage",
36
- title: "Set Working Image",
37
- handler: (id: string, updates: Record<string, any>) => {
38
- tool.updateImageInWorking(id, updates);
47
+ command: "setImageTransform",
48
+ id: "setImageTransform",
49
+ title: "Set Image Transform",
50
+ handler: async (
51
+ id: string,
52
+ updates: Record<string, any>,
53
+ options: Record<string, any> = {},
54
+ ) => {
55
+ await tool.setImageTransform(id, updates, options);
39
56
  },
40
57
  },
41
58
  {
42
- command: "resetWorkingImages",
43
- id: "resetWorkingImages",
44
- title: "Reset Working Images",
59
+ command: "imageSessionReset",
60
+ id: "imageSessionReset",
61
+ title: "Reset Image Session",
45
62
  handler: () => {
46
- tool.workingItems = tool.cloneItems(tool.items);
47
- tool.hasWorkingChanges = false;
48
- tool.updateImages();
49
- tool.emitWorkingChange();
63
+ tool.resetImageSession();
50
64
  },
51
65
  },
52
66
  {
@@ -65,30 +79,6 @@ export function createImageCommands(tool: any): CommandContribution[] {
65
79
  return await tool.exportUserCroppedImage(options);
66
80
  },
67
81
  },
68
- {
69
- command: "fitImageToArea",
70
- id: "fitImageToArea",
71
- title: "Fit Image to Area",
72
- handler: async (
73
- id: string,
74
- area: {
75
- width: number;
76
- height: number;
77
- left?: number;
78
- top?: number;
79
- },
80
- ) => {
81
- await tool.fitImageToArea(id, area);
82
- },
83
- },
84
- {
85
- command: "fitImageToDefaultArea",
86
- id: "fitImageToDefaultArea",
87
- title: "Fit Image to Default Area",
88
- handler: async (id: string) => {
89
- await tool.fitImageToDefaultArea(id);
90
- },
91
- },
92
82
  {
93
83
  command: "focusImage",
94
84
  id: "focusImage",
@@ -105,9 +95,10 @@ export function createImageCommands(tool: any): CommandContribution[] {
105
95
  id: "removeImage",
106
96
  title: "Remove Image",
107
97
  handler: (id: string) => {
108
- const removed = tool.items.find((item: any) => item.id === id);
109
- const next = tool.items.filter((item: any) => item.id !== id);
110
- if (next.length !== tool.items.length) {
98
+ const sourceItems = tool.isToolActive ? tool.workingItems : tool.items;
99
+ const removed = sourceItems.find((item: any) => item.id === id);
100
+ const next = sourceItems.filter((item: any) => item.id !== id);
101
+ if (next.length !== sourceItems.length) {
111
102
  tool.purgeSourceSizeCacheForItem(removed);
112
103
  if (tool.focusedImageId === id) {
113
104
  tool.setImageFocus(null, {
@@ -115,6 +106,13 @@ export function createImageCommands(tool: any): CommandContribution[] {
115
106
  skipRender: true,
116
107
  });
117
108
  }
109
+ if (tool.isToolActive) {
110
+ tool.workingItems = tool.cloneItems(next);
111
+ tool.hasWorkingChanges = true;
112
+ tool.updateImages();
113
+ tool.emitWorkingChange(id);
114
+ return;
115
+ }
118
116
  tool.updateConfig(next);
119
117
  }
120
118
  },
@@ -141,6 +139,13 @@ export function createImageCommands(tool: any): CommandContribution[] {
141
139
  syncCanvasSelection: true,
142
140
  skipRender: true,
143
141
  });
142
+ if (tool.isToolActive) {
143
+ tool.workingItems = [];
144
+ tool.hasWorkingChanges = true;
145
+ tool.updateImages();
146
+ tool.emitWorkingChange();
147
+ return;
148
+ }
144
149
  tool.updateConfig([]);
145
150
  },
146
151
  },
@@ -149,11 +154,19 @@ export function createImageCommands(tool: any): CommandContribution[] {
149
154
  id: "bringToFront",
150
155
  title: "Bring Image to Front",
151
156
  handler: (id: string) => {
152
- const index = tool.items.findIndex((item: any) => item.id === id);
153
- if (index !== -1 && index < tool.items.length - 1) {
154
- const next = [...tool.items];
157
+ const sourceItems = tool.isToolActive ? tool.workingItems : tool.items;
158
+ const index = sourceItems.findIndex((item: any) => item.id === id);
159
+ if (index !== -1 && index < sourceItems.length - 1) {
160
+ const next = [...sourceItems];
155
161
  const [item] = next.splice(index, 1);
156
162
  next.push(item);
163
+ if (tool.isToolActive) {
164
+ tool.workingItems = tool.cloneItems(next);
165
+ tool.hasWorkingChanges = true;
166
+ tool.updateImages();
167
+ tool.emitWorkingChange(id);
168
+ return;
169
+ }
157
170
  tool.updateConfig(next);
158
171
  }
159
172
  },
@@ -163,11 +176,19 @@ export function createImageCommands(tool: any): CommandContribution[] {
163
176
  id: "sendToBack",
164
177
  title: "Send Image to Back",
165
178
  handler: (id: string) => {
166
- const index = tool.items.findIndex((item: any) => item.id === id);
179
+ const sourceItems = tool.isToolActive ? tool.workingItems : tool.items;
180
+ const index = sourceItems.findIndex((item: any) => item.id === id);
167
181
  if (index > 0) {
168
- const next = [...tool.items];
182
+ const next = [...sourceItems];
169
183
  const [item] = next.splice(index, 1);
170
184
  next.unshift(item);
185
+ if (tool.isToolActive) {
186
+ tool.workingItems = tool.cloneItems(next);
187
+ tool.hasWorkingChanges = true;
188
+ tool.updateImages();
189
+ tool.emitWorkingChange(id);
190
+ return;
191
+ }
171
192
  tool.updateConfig(next);
172
193
  }
173
194
  },