@umituz/react-native-image 1.3.8 → 1.3.10

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.
Files changed (23) hide show
  1. package/package.json +8 -2
  2. package/src/index.ts +5 -4
  3. package/src/infrastructure/services/ImageConversionService.ts +7 -32
  4. package/src/infrastructure/services/ImageEditorService.ts +41 -179
  5. package/src/infrastructure/services/{ImageAIEnhancementService.ts → ImageEnhanceService.ts} +42 -55
  6. package/src/infrastructure/services/ImageTemplateService.ts +10 -4
  7. package/src/infrastructure/services/ImageTransformService.ts +43 -93
  8. package/src/infrastructure/utils/FilterProcessor.ts +26 -263
  9. package/src/infrastructure/utils/{AIImageAnalysisUtils.ts → ImageAnalysisUtils.ts} +3 -3
  10. package/src/infrastructure/utils/ImageEditorHistoryUtils.ts +63 -0
  11. package/src/infrastructure/utils/ImageFilterUtils.ts +152 -0
  12. package/src/infrastructure/utils/ImageTransformUtils.ts +25 -0
  13. package/src/infrastructure/utils/LayerManager.ts +0 -81
  14. package/src/presentation/components/editor/FilterPickerSheet.tsx +75 -0
  15. package/src/presentation/components/editor/StickerPickerSheet.tsx +62 -0
  16. package/src/presentation/components/editor/TextEditorSheet.tsx +98 -0
  17. package/src/presentation/components/editor/TextEditorTabs.tsx +111 -0
  18. package/src/presentation/hooks/useImage.ts +5 -8
  19. package/src/presentation/hooks/useImageEnhance.ts +32 -0
  20. package/src/presentation/hooks/useImageTransform.ts +3 -4
  21. package/src/infrastructure/services/ImageAdvancedTransformService.ts +0 -106
  22. package/src/infrastructure/services/ImageSpecializedEnhancementService.ts +0 -57
  23. package/src/presentation/hooks/useImageAIEnhancement.ts +0 -33
@@ -9,131 +9,81 @@ import type {
9
9
  ImageManipulateAction,
10
10
  ImageSaveOptions,
11
11
  ImageManipulationResult,
12
- SaveFormat,
13
12
  ImageCropArea,
14
13
  ImageFlipOptions,
15
14
  } from '../../domain/entities/ImageTypes';
16
- import { IMAGE_CONSTANTS } from '../../domain/entities/ImageConstants';
17
15
  import { ImageUtils } from '../../domain/utils/ImageUtils';
18
16
  import { ImageValidator } from '../utils/ImageValidator';
19
17
  import { ImageErrorHandler, IMAGE_ERROR_CODES } from '../utils/ImageErrorHandler';
18
+ import { ImageTransformUtils } from '../utils/ImageTransformUtils';
20
19
 
21
20
  export class ImageTransformService {
22
- private static mapFormat(format?: SaveFormat): ImageManipulator.SaveFormat {
23
- if (format === 'png') return ImageManipulator.SaveFormat.PNG;
24
- if (format === 'webp') return ImageManipulator.SaveFormat.WEBP;
25
- return ImageManipulator.SaveFormat.JPEG;
26
- }
27
-
28
- private static buildSaveOptions(options?: ImageSaveOptions): ImageManipulator.SaveOptions {
29
- return {
30
- compress: options?.compress ?? IMAGE_CONSTANTS.defaultQuality,
31
- format: this.mapFormat(options?.format),
32
- base64: options?.base64,
33
- };
34
- }
35
-
36
- static async resize(
37
- uri: string,
38
- width?: number,
39
- height?: number,
40
- options?: ImageSaveOptions
41
- ): Promise<ImageManipulationResult> {
21
+ static async resize(uri: string, width?: number, height?: number, options?: ImageSaveOptions): Promise<ImageManipulationResult> {
42
22
  try {
43
- const uriValidation = ImageValidator.validateUri(uri);
44
- if (!uriValidation.isValid) {
45
- throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'resize');
46
- }
47
-
48
- const dimValidation = ImageValidator.validateDimensions({ width, height });
49
- if (!dimValidation.isValid) {
50
- throw ImageErrorHandler.createError(dimValidation.error!, IMAGE_ERROR_CODES.INVALID_DIMENSIONS, 'resize');
51
- }
52
-
53
- return await ImageManipulator.manipulateAsync(
54
- uri,
55
- [{ resize: { width, height } }],
56
- this.buildSaveOptions(options)
57
- );
23
+ ImageValidator.validateUri(uri);
24
+ ImageValidator.validateDimensions({ width, height });
25
+ return await ImageManipulator.manipulateAsync(uri, [{ resize: { width, height } }], ImageTransformUtils.buildSaveOptions(options));
58
26
  } catch (error) {
59
27
  throw ImageErrorHandler.handleUnknownError(error, 'resize');
60
28
  }
61
29
  }
62
30
 
63
- static async crop(
64
- uri: string,
65
- cropArea: ImageCropArea,
66
- options?: ImageSaveOptions
67
- ): Promise<ImageManipulationResult> {
31
+ static async crop(uri: string, cropArea: ImageCropArea, options?: ImageSaveOptions): Promise<ImageManipulationResult> {
68
32
  try {
69
- const uriValidation = ImageValidator.validateUri(uri);
70
- if (!uriValidation.isValid) {
71
- throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'crop');
72
- }
73
-
74
- const dimValidation = ImageValidator.validateDimensions(cropArea);
75
- if (!dimValidation.isValid) {
76
- throw ImageErrorHandler.createError(dimValidation.error!, IMAGE_ERROR_CODES.INVALID_DIMENSIONS, 'crop');
77
- }
78
-
79
- return await ImageManipulator.manipulateAsync(
80
- uri,
81
- [{ crop: cropArea }],
82
- this.buildSaveOptions(options)
83
- );
33
+ ImageValidator.validateUri(uri);
34
+ ImageValidator.validateDimensions(cropArea);
35
+ return await ImageManipulator.manipulateAsync(uri, [{ crop: cropArea }], ImageTransformUtils.buildSaveOptions(options));
84
36
  } catch (error) {
85
37
  throw ImageErrorHandler.handleUnknownError(error, 'crop');
86
38
  }
87
39
  }
88
40
 
89
- static async rotate(
90
- uri: string,
91
- degrees: number,
92
- options?: ImageSaveOptions
93
- ): Promise<ImageManipulationResult> {
41
+ static async rotate(uri: string, degrees: number, options?: ImageSaveOptions): Promise<ImageManipulationResult> {
94
42
  try {
95
- const uriValidation = ImageValidator.validateUri(uri);
96
- if (!uriValidation.isValid) {
97
- throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'rotate');
98
- }
99
-
100
- const rotationValidation = ImageValidator.validateRotation(degrees);
101
- if (!rotationValidation.isValid) {
102
- throw ImageErrorHandler.createError(rotationValidation.error!, IMAGE_ERROR_CODES.VALIDATION_ERROR, 'rotate');
103
- }
104
-
105
- return await ImageManipulator.manipulateAsync(
106
- uri,
107
- [{ rotate: degrees }],
108
- this.buildSaveOptions(options)
109
- );
43
+ ImageValidator.validateUri(uri);
44
+ ImageValidator.validateRotation(degrees);
45
+ return await ImageManipulator.manipulateAsync(uri, [{ rotate: degrees }], ImageTransformUtils.buildSaveOptions(options));
110
46
  } catch (error) {
111
47
  throw ImageErrorHandler.handleUnknownError(error, 'rotate');
112
48
  }
113
49
  }
114
50
 
115
- static async flip(
116
- uri: string,
117
- flip: ImageFlipOptions,
118
- options?: ImageSaveOptions
119
- ): Promise<ImageManipulationResult> {
51
+ static async flip(uri: string, flip: ImageFlipOptions, options?: ImageSaveOptions): Promise<ImageManipulationResult> {
120
52
  try {
121
- const uriValidation = ImageValidator.validateUri(uri);
122
- if (!uriValidation.isValid) {
123
- throw ImageErrorHandler.createError(uriValidation.error!, IMAGE_ERROR_CODES.INVALID_URI, 'flip');
124
- }
125
-
53
+ ImageValidator.validateUri(uri);
126
54
  const actions: ImageManipulator.Action[] = [];
127
55
  if (flip.horizontal) actions.push({ flip: ImageManipulator.FlipType.Horizontal });
128
56
  if (flip.vertical) actions.push({ flip: ImageManipulator.FlipType.Vertical });
129
-
130
- return await ImageManipulator.manipulateAsync(
131
- uri,
132
- actions,
133
- this.buildSaveOptions(options)
134
- );
57
+ return await ImageManipulator.manipulateAsync(uri, actions, ImageTransformUtils.buildSaveOptions(options));
135
58
  } catch (error) {
136
59
  throw ImageErrorHandler.handleUnknownError(error, 'flip');
137
60
  }
138
61
  }
62
+
63
+ static async manipulate(uri: string, action: ImageManipulateAction, options?: ImageSaveOptions): Promise<ImageManipulationResult> {
64
+ try {
65
+ ImageValidator.validateUri(uri);
66
+ const actions: ImageManipulator.Action[] = [];
67
+ if (action.resize) actions.push({ resize: action.resize });
68
+ if (action.crop) actions.push({ crop: action.crop });
69
+ if (action.rotate) actions.push({ rotate: action.rotate });
70
+ if (action.flip) {
71
+ if (action.flip.horizontal) actions.push({ flip: ImageManipulator.FlipType.Horizontal });
72
+ if (action.flip.vertical) actions.push({ flip: ImageManipulator.FlipType.Vertical });
73
+ }
74
+ return await ImageManipulator.manipulateAsync(uri, actions, ImageTransformUtils.buildSaveOptions(options));
75
+ } catch (error) {
76
+ throw ImageErrorHandler.handleUnknownError(error, 'manipulate');
77
+ }
78
+ }
79
+
80
+ static async resizeToFit(uri: string, maxWidth: number, maxHeight: number, options?: ImageSaveOptions): Promise<ImageManipulationResult> {
81
+ const dimensions = ImageUtils.fitToSize(maxWidth, maxHeight, maxWidth, maxHeight);
82
+ return this.resize(uri, dimensions.width, dimensions.height, options);
83
+ }
84
+
85
+ static async cropToSquare(uri: string, width: number, height: number, options?: ImageSaveOptions): Promise<ImageManipulationResult> {
86
+ const cropArea = ImageUtils.getSquareCrop(width, height);
87
+ return this.crop(uri, cropArea, options);
88
+ }
139
89
  }
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * Infrastructure - Filter Processor
3
3
  *
4
- * Real-time filter processing with preview
4
+ * Filter processing with preset management
5
5
  */
6
6
 
7
+ import { ImageFilterUtils } from './ImageFilterUtils';
8
+
7
9
  export interface FilterPreset {
8
10
  id: string;
9
11
  name: string;
@@ -35,92 +37,39 @@ export class FilterProcessor {
35
37
  id: 'brightness',
36
38
  name: 'Brightness',
37
39
  category: 'basic',
38
- parameters: [
39
- {
40
- name: 'brightness',
41
- type: 'slider',
42
- min: -100,
43
- max: 100,
44
- value: 0,
45
- label: 'Brightness',
46
- },
47
- ],
40
+ parameters: [{ name: 'brightness', type: 'slider', min: -100, max: 100, value: 0, label: 'Brightness' }],
48
41
  },
49
42
  {
50
43
  id: 'contrast',
51
44
  name: 'Contrast',
52
45
  category: 'basic',
53
- parameters: [
54
- {
55
- name: 'contrast',
56
- type: 'slider',
57
- min: -100,
58
- max: 100,
59
- value: 0,
60
- label: 'Contrast',
61
- },
62
- ],
46
+ parameters: [{ name: 'contrast', type: 'slider', min: -100, max: 100, value: 0, label: 'Contrast' }],
63
47
  },
64
48
  {
65
49
  id: 'saturation',
66
50
  name: 'Saturation',
67
51
  category: 'color',
68
- parameters: [
69
- {
70
- name: 'saturation',
71
- type: 'slider',
72
- min: -100,
73
- max: 100,
74
- value: 0,
75
- label: 'Saturation',
76
- },
77
- ],
52
+ parameters: [{ name: 'saturation', type: 'slider', min: -100, max: 100, value: 0, label: 'Saturation' }],
78
53
  },
79
54
  {
80
55
  id: 'vintage',
81
56
  name: 'Vintage',
82
57
  category: 'vintage',
83
58
  parameters: [
84
- {
85
- name: 'intensity',
86
- type: 'slider',
87
- min: 0,
88
- max: 100,
89
- value: 50,
90
- label: 'Intensity',
91
- },
92
- {
93
- name: 'warmth',
94
- type: 'slider',
95
- min: 0,
96
- max: 100,
97
- value: 30,
98
- label: 'Warmth',
99
- },
59
+ { name: 'intensity', type: 'slider', min: 0, max: 100, value: 50, label: 'Intensity' },
60
+ { name: 'warmth', type: 'slider', min: 0, max: 100, value: 30, label: 'Warmth' },
100
61
  ],
101
62
  },
102
63
  {
103
64
  id: 'blur',
104
65
  name: 'Blur',
105
66
  category: 'artistic',
106
- parameters: [
107
- {
108
- name: 'radius',
109
- type: 'slider',
110
- min: 0,
111
- max: 20,
112
- value: 0,
113
- label: 'Blur Radius',
114
- },
115
- ],
67
+ parameters: [{ name: 'radius', type: 'slider', min: 0, max: 20, value: 0, label: 'Blur Radius' }],
116
68
  },
117
69
  ];
118
70
 
119
71
  static getPresets(category?: string): FilterPreset[] {
120
- if (category) {
121
- return this.PRESETS.filter(preset => preset.category === category);
122
- }
123
- return this.PRESETS;
72
+ return category ? this.PRESETS.filter(preset => preset.category === category) : this.PRESETS;
124
73
  }
125
74
 
126
75
  static getPreset(id: string): FilterPreset | undefined {
@@ -129,233 +78,47 @@ export class FilterProcessor {
129
78
 
130
79
  static createFilterState(presetId: string): FilterState {
131
80
  const preset = this.getPreset(presetId);
132
- if (!preset) {
133
- throw new Error(`Filter preset not found: ${presetId}`);
134
- }
81
+ if (!preset) throw new Error(`Filter preset not found: ${presetId}`);
135
82
 
136
83
  const parameters: Record<string, any> = {};
137
- preset.parameters.forEach(param => {
138
- parameters[param.name] = param.value;
139
- });
84
+ preset.parameters.forEach(param => { parameters[param.name] = param.value; });
140
85
 
141
- return {
142
- id: presetId,
143
- intensity: 100,
144
- parameters,
145
- enabled: true,
146
- };
86
+ return { id: presetId, intensity: 100, parameters, enabled: true };
147
87
  }
148
88
 
149
89
  static applyFilter(
150
- imageData: ImageData,
90
+ imageData: Uint8ClampedArray,
91
+ width: number,
92
+ height: number,
151
93
  filterState: FilterState
152
- ): ImageData {
94
+ ): Uint8ClampedArray {
153
95
  const preset = this.getPreset(filterState.id);
154
- if (!preset || !filterState.enabled) {
155
- return imageData;
156
- }
96
+ if (!preset || !filterState.enabled) return imageData;
157
97
 
158
- let processedData = new Uint8ClampedArray(imageData.data);
98
+ let processedData = new Uint8ClampedArray(imageData);
159
99
 
160
- // Apply filter based on preset type
161
100
  switch (filterState.id) {
162
101
  case 'brightness':
163
- processedData = this.applyBrightness(processedData, filterState.parameters.brightness) as any;
102
+ processedData = ImageFilterUtils.applyBrightness(processedData, filterState.parameters.brightness) as any;
164
103
  break;
165
104
  case 'contrast':
166
- processedData = this.applyContrast(processedData, filterState.parameters.contrast) as any;
105
+ processedData = ImageFilterUtils.applyContrast(processedData, filterState.parameters.contrast) as any;
167
106
  break;
168
107
  case 'saturation':
169
- processedData = this.applySaturation(processedData, filterState.parameters.saturation) as any;
108
+ processedData = ImageFilterUtils.applySaturation(processedData, filterState.parameters.saturation) as any;
170
109
  break;
171
110
  case 'vintage':
172
- processedData = this.applyVintage(processedData, filterState.parameters.intensity, filterState.parameters.warmth) as any;
111
+ processedData = ImageFilterUtils.applyVintage(processedData, filterState.parameters.intensity, filterState.parameters.warmth) as any;
173
112
  break;
174
113
  case 'blur':
175
- processedData = this.applyBlur(processedData, filterState.parameters.radius) as any;
114
+ processedData = ImageFilterUtils.applyBlur(processedData, filterState.parameters.radius, width, height) as any;
176
115
  break;
177
116
  }
178
117
 
179
- // Apply intensity
180
118
  if (filterState.intensity < 100) {
181
- processedData = this.applyIntensity(imageData.data, processedData, filterState.intensity / 100) as any;
182
- }
183
-
184
- return {
185
- ...imageData,
186
- data: processedData,
187
- };
188
- }
189
-
190
- private static applyBrightness(
191
- data: Uint8ClampedArray,
192
- brightness: number
193
- ): Uint8ClampedArray {
194
- const result = new Uint8ClampedArray(data);
195
- const adjustment = brightness * 2.55; // Convert -100 to 100 to -255 to 255
196
-
197
- for (let i = 0; i < result.length; i += 4) {
198
- result[i] = Math.min(255, Math.max(0, result[i] + adjustment));
199
- result[i + 1] = Math.min(255, Math.max(0, result[i + 1] + adjustment));
200
- result[i + 2] = Math.min(255, Math.max(0, result[i + 2] + adjustment));
201
- }
202
-
203
- return result;
204
- }
205
-
206
- private static applyContrast(
207
- data: Uint8ClampedArray,
208
- contrast: number
209
- ): Uint8ClampedArray {
210
- const result = new Uint8ClampedArray(data);
211
- const factor = (259 * (contrast * 255 + 255)) / (255 * (259 - contrast * 255));
212
-
213
- for (let i = 0; i < result.length; i += 4) {
214
- result[i] = Math.min(255, Math.max(0, factor * (result[i] - 128) + 128));
215
- result[i + 1] = Math.min(255, Math.max(0, factor * (result[i + 1] - 128) + 128));
216
- result[i + 2] = Math.min(255, Math.max(0, factor * (result[i + 2] - 128) + 128));
217
- }
218
-
219
- return result;
220
- }
221
-
222
- private static applySaturation(
223
- data: Uint8ClampedArray,
224
- saturation: number
225
- ): Uint8ClampedArray {
226
- const result = new Uint8ClampedArray(data);
227
- const adjustment = 1 + (saturation / 100);
228
-
229
- for (let i = 0; i < result.length; i += 4) {
230
- const r = result[i];
231
- const g = result[i + 1];
232
- const b = result[i + 2];
233
-
234
- const gray = 0.299 * r + 0.587 * g + 0.114 * b;
235
-
236
- result[i] = Math.min(255, Math.max(0, gray + adjustment * (r - gray)));
237
- result[i + 1] = Math.min(255, Math.max(0, gray + adjustment * (g - gray)));
238
- result[i + 2] = Math.min(255, Math.max(0, gray + adjustment * (b - gray)));
239
- }
240
-
241
- return result;
242
- }
243
-
244
- private static applyVintage(
245
- data: Uint8ClampedArray,
246
- intensity: number,
247
- warmth: number
248
- ): Uint8ClampedArray {
249
- const result = new Uint8ClampedArray(data);
250
- const factor = intensity / 100;
251
- const warmFactor = warmth / 100;
252
-
253
- for (let i = 0; i < result.length; i += 4) {
254
- let r = result[i];
255
- let g = result[i + 1];
256
- let b = result[i + 2];
257
-
258
- // Apply sepia effect
259
- const tr = 0.393 * r + 0.769 * g + 0.189 * b;
260
- const tg = 0.349 * r + 0.686 * g + 0.168 * b;
261
- const tb = 0.272 * r + 0.534 * g + 0.131 * b;
262
-
263
- // Mix with original based on intensity
264
- r = r * (1 - factor) + tr * factor;
265
- g = g * (1 - factor) + tg * factor;
266
- b = b * (1 - factor) + tb * factor;
267
-
268
- // Apply warmth
269
- if (warmFactor > 0) {
270
- result[i] = Math.min(255, r + warmFactor * 20);
271
- result[i + 1] = Math.min(255, g + warmFactor * 10);
272
- result[i + 2] = Math.min(255, b * (1 - warmFactor * 0.3));
273
- } else {
274
- result[i] = r;
275
- result[i + 1] = g;
276
- result[i + 2] = Math.min(255, b * (1 - Math.abs(warmFactor) * 0.3));
277
- }
119
+ processedData = ImageFilterUtils.applyIntensity(imageData, processedData, filterState.intensity / 100) as any;
278
120
  }
279
121
 
280
- return result;
281
- }
282
-
283
- private static applyBlur(
284
- data: Uint8ClampedArray,
285
- radius: number
286
- ): Uint8ClampedArray {
287
- // Simple box blur implementation
288
- const result = new Uint8ClampedArray(data);
289
- const width = Math.sqrt(data.length / 4);
290
- const height = width;
291
- const size = Math.floor(radius) || 1;
292
-
293
- for (let y = 0; y < height; y++) {
294
- for (let x = 0; x < width; x++) {
295
- let r = 0, g = 0, b = 0, a = 0;
296
- let count = 0;
297
-
298
- for (let dy = -size; dy <= size; dy++) {
299
- for (let dx = -size; dx <= size; dx++) {
300
- const ny = y + dy;
301
- const nx = x + dx;
302
-
303
- if (ny >= 0 && ny < height && nx >= 0 && nx < width) {
304
- const idx = (ny * width + nx) * 4;
305
- r += data[idx];
306
- g += data[idx + 1];
307
- b += data[idx + 2];
308
- a += data[idx + 3];
309
- count++;
310
- }
311
- }
312
- }
313
-
314
- const idx = (y * width + x) * 4;
315
- result[idx] = r / count;
316
- result[idx + 1] = g / count;
317
- result[idx + 2] = b / count;
318
- result[idx + 3] = a / count;
319
- }
320
- }
321
-
322
- return result;
323
- }
324
-
325
- private static applyIntensity(
326
- originalData: Uint8ClampedArray,
327
- processedData: Uint8ClampedArray,
328
- intensity: number
329
- ): Uint8ClampedArray {
330
- const result = new Uint8ClampedArray(originalData.length);
331
-
332
- for (let i = 0; i < originalData.length; i++) {
333
- result[i] = originalData[i] * (1 - intensity) + processedData[i] * intensity;
334
- }
335
-
336
- return result;
337
- }
338
-
339
- static createPreview(
340
- imageData: ImageData,
341
- filterState: FilterState,
342
- previewSize: { width: number; height: number }
343
- ): ImageData {
344
- // Create a smaller preview version
345
- const previewCanvas = document.createElement('canvas') || {} as any;
346
- previewCanvas.width = previewSize.width;
347
- previewCanvas.height = previewSize.height;
348
- const ctx = previewCanvas.getContext('2d');
349
-
350
- if (!ctx) return imageData;
351
-
352
- // Scale down the image for preview
353
- ctx.drawImage(
354
- {} as HTMLImageElement, // Would be the actual image
355
- 0, 0, previewSize.width, previewSize.height
356
- );
357
-
358
- const previewImageData = ctx.getImageData(0, 0, previewSize.width, previewSize.height);
359
- return this.applyFilter(previewImageData, filterState);
122
+ return processedData as any;
360
123
  }
361
124
  }
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Image Infrastructure - AI Image Analysis
2
+ * Image Infrastructure - Image Analysis
3
3
  *
4
- * Advanced image analysis utilities
4
+ * Image analysis utilities
5
5
  */
6
6
 
7
- export class AIImageAnalysisUtils {
7
+ export class ImageAnalysisUtils {
8
8
  static calculateColorBalance(imageData: Uint8ClampedArray): {
9
9
  redBalance: number;
10
10
  greenBalance: number;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Infrastructure - Editor History Utils
3
+ *
4
+ * Logic for managing editor state history and undo/redo
5
+ */
6
+
7
+ import { type EditorState, type EditorHistory } from '../../domain/entities/EditorTypes';
8
+
9
+ export class ImageEditorHistoryUtils {
10
+ static addToHistory(
11
+ state: EditorState,
12
+ newHistory: EditorHistory,
13
+ maxHistory: number = 50
14
+ ): EditorState {
15
+ const newHistoryArray = [...state.history.slice(0, state.historyIndex + 1), newHistory];
16
+
17
+ if (newHistoryArray.length > maxHistory) {
18
+ newHistoryArray.shift();
19
+ }
20
+
21
+ return {
22
+ ...state,
23
+ history: newHistoryArray,
24
+ historyIndex: newHistoryArray.length - 1,
25
+ };
26
+ }
27
+
28
+ static undo(state: EditorState): EditorState {
29
+ if (state.historyIndex <= 0) return state;
30
+
31
+ const newIndex = state.historyIndex - 1;
32
+ const historyState = state.history[newIndex];
33
+
34
+ return {
35
+ ...state,
36
+ layers: historyState.layers,
37
+ historyIndex: newIndex,
38
+ isDirty: true,
39
+ };
40
+ }
41
+
42
+ static redo(state: EditorState): EditorState {
43
+ if (state.historyIndex >= state.history.length - 1) return state;
44
+
45
+ const newIndex = state.historyIndex + 1;
46
+ const historyState = state.history[newIndex];
47
+
48
+ return {
49
+ ...state,
50
+ layers: historyState.layers,
51
+ historyIndex: newIndex,
52
+ isDirty: true,
53
+ };
54
+ }
55
+
56
+ static canUndo(state: EditorState): boolean {
57
+ return state.historyIndex > 0;
58
+ }
59
+
60
+ static canRedo(state: EditorState): boolean {
61
+ return state.historyIndex < state.history.length - 1;
62
+ }
63
+ }