tokimeki-image-editor 0.1.1 → 0.1.3

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 (39) hide show
  1. package/dist/components/AdjustTool.svelte +317 -0
  2. package/dist/components/AdjustTool.svelte.d.ts +9 -0
  3. package/dist/components/BlurTool.svelte +613 -0
  4. package/dist/components/BlurTool.svelte.d.ts +15 -0
  5. package/dist/components/Canvas.svelte +214 -0
  6. package/dist/components/Canvas.svelte.d.ts +17 -0
  7. package/dist/components/CropTool.svelte +942 -0
  8. package/dist/components/CropTool.svelte.d.ts +14 -0
  9. package/dist/components/ExportTool.svelte +191 -0
  10. package/dist/components/ExportTool.svelte.d.ts +10 -0
  11. package/dist/components/FilterTool.svelte +492 -0
  12. package/dist/components/FilterTool.svelte.d.ts +12 -0
  13. package/dist/components/ImageEditor.svelte +735 -0
  14. package/dist/components/ImageEditor.svelte.d.ts +12 -0
  15. package/dist/components/RotateTool.svelte +157 -0
  16. package/dist/components/RotateTool.svelte.d.ts +9 -0
  17. package/dist/components/StampTool.svelte +678 -0
  18. package/dist/components/StampTool.svelte.d.ts +15 -0
  19. package/dist/components/Toolbar.svelte +136 -0
  20. package/dist/components/Toolbar.svelte.d.ts +10 -0
  21. package/dist/config/stamps.d.ts +2 -0
  22. package/dist/config/stamps.js +22 -0
  23. package/dist/i18n/index.d.ts +1 -0
  24. package/dist/i18n/index.js +9 -0
  25. package/dist/i18n/locales/en.json +68 -0
  26. package/dist/i18n/locales/ja.json +68 -0
  27. package/dist/index.d.ts +4 -0
  28. package/dist/index.js +5 -0
  29. package/dist/types.d.ts +97 -0
  30. package/dist/types.js +1 -0
  31. package/dist/utils/adjustments.d.ts +26 -0
  32. package/dist/utils/adjustments.js +525 -0
  33. package/dist/utils/canvas.d.ts +30 -0
  34. package/dist/utils/canvas.js +293 -0
  35. package/dist/utils/filters.d.ts +18 -0
  36. package/dist/utils/filters.js +114 -0
  37. package/dist/utils/history.d.ts +15 -0
  38. package/dist/utils/history.js +67 -0
  39. package/package.json +1 -1
@@ -0,0 +1,293 @@
1
+ import { applyAllAdjustments, applyGaussianBlur } from './adjustments';
2
+ // Image cache for stamp images
3
+ const stampImageCache = new Map();
4
+ export function preloadStampImage(url) {
5
+ // Return cached image if available
6
+ if (stampImageCache.has(url)) {
7
+ return Promise.resolve(stampImageCache.get(url));
8
+ }
9
+ // Load new image
10
+ return new Promise((resolve, reject) => {
11
+ const img = new Image();
12
+ img.crossOrigin = 'anonymous'; // Allow CORS for external images
13
+ img.onload = () => {
14
+ stampImageCache.set(url, img);
15
+ resolve(img);
16
+ };
17
+ img.onerror = (error) => {
18
+ console.error(`Failed to load stamp image: ${url}`, error);
19
+ reject(error);
20
+ };
21
+ img.src = url;
22
+ });
23
+ }
24
+ export function getStampImage(url) {
25
+ return stampImageCache.get(url) || null;
26
+ }
27
+ export function loadImage(file) {
28
+ return new Promise((resolve, reject) => {
29
+ const reader = new FileReader();
30
+ reader.onload = (e) => {
31
+ const img = new Image();
32
+ img.onload = () => resolve(img);
33
+ img.onerror = reject;
34
+ img.src = e.target?.result;
35
+ };
36
+ reader.onerror = reject;
37
+ reader.readAsDataURL(file);
38
+ });
39
+ }
40
+ export function calculateFitScale(imageWidth, imageHeight, canvasWidth, canvasHeight) {
41
+ const scaleX = canvasWidth / imageWidth;
42
+ const scaleY = canvasHeight / imageHeight;
43
+ return Math.min(scaleX, scaleY, 1); // Don't scale up, only down
44
+ }
45
+ export function drawImage(canvas, img, viewport, transform, adjustments, cropArea, blurAreas, stampAreas) {
46
+ const ctx = canvas.getContext('2d');
47
+ if (!ctx)
48
+ return;
49
+ // Clear canvas
50
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
51
+ // Ensure filter is reset before starting
52
+ ctx.filter = 'none';
53
+ ctx.save();
54
+ // Apply viewport transformations
55
+ const centerX = canvas.width / 2;
56
+ const centerY = canvas.height / 2;
57
+ ctx.translate(centerX + viewport.offsetX, centerY + viewport.offsetY);
58
+ // Apply zoom
59
+ const totalScale = viewport.scale * viewport.zoom;
60
+ ctx.scale(totalScale, totalScale);
61
+ // Rotation
62
+ ctx.rotate((transform.rotation * Math.PI) / 180);
63
+ // Flip
64
+ ctx.scale(transform.flipHorizontal ? -1 : 1, transform.flipVertical ? -1 : 1);
65
+ // Draw image (with crop if specified)
66
+ if (cropArea) {
67
+ // Draw only the cropped area
68
+ ctx.drawImage(img, cropArea.x, cropArea.y, cropArea.width, cropArea.height, -cropArea.width / 2, -cropArea.height / 2, cropArea.width, cropArea.height);
69
+ }
70
+ else {
71
+ // Draw full image centered
72
+ ctx.drawImage(img, -img.width / 2, -img.height / 2);
73
+ }
74
+ ctx.restore();
75
+ // Apply all adjustments via pixel manipulation (Safari-compatible)
76
+ // This modifies the canvas pixels after drawing
77
+ applyAllAdjustments(canvas, img, viewport, adjustments, cropArea);
78
+ // Apply blur areas
79
+ if (blurAreas && blurAreas.length > 0) {
80
+ applyBlurAreas(canvas, img, viewport, blurAreas, cropArea);
81
+ }
82
+ // Apply stamps
83
+ if (stampAreas && stampAreas.length > 0) {
84
+ applyStamps(canvas, img, viewport, stampAreas, cropArea);
85
+ }
86
+ }
87
+ export function exportCanvas(canvas, options) {
88
+ if (options.format === 'jpeg') {
89
+ return canvas.toDataURL('image/jpeg', options.quality);
90
+ }
91
+ return canvas.toDataURL('image/png');
92
+ }
93
+ export function downloadImage(dataUrl, filename) {
94
+ const link = document.createElement('a');
95
+ link.download = filename;
96
+ link.href = dataUrl;
97
+ link.click();
98
+ }
99
+ export function applyTransform(img, transform, adjustments, cropArea = null, blurAreas = [], stampAreas = []) {
100
+ const canvas = document.createElement('canvas');
101
+ const ctx = canvas.getContext('2d');
102
+ if (!ctx)
103
+ return canvas;
104
+ // Calculate source dimensions
105
+ const sourceWidth = cropArea ? cropArea.width : img.width;
106
+ const sourceHeight = cropArea ? cropArea.height : img.height;
107
+ // Calculate canvas size based on rotation
108
+ const needsSwap = transform.rotation === 90 || transform.rotation === 270;
109
+ canvas.width = needsSwap ? sourceHeight : sourceWidth;
110
+ canvas.height = needsSwap ? sourceWidth : sourceHeight;
111
+ // Ensure filter is reset before starting
112
+ ctx.filter = 'none';
113
+ ctx.save();
114
+ ctx.translate(canvas.width / 2, canvas.height / 2);
115
+ ctx.rotate((transform.rotation * Math.PI) / 180);
116
+ ctx.scale(transform.flipHorizontal ? -1 : 1, transform.flipVertical ? -1 : 1);
117
+ if (cropArea) {
118
+ ctx.drawImage(img, cropArea.x, cropArea.y, cropArea.width, cropArea.height, -sourceWidth / 2, -sourceHeight / 2, sourceWidth, sourceHeight);
119
+ }
120
+ else {
121
+ ctx.drawImage(img, -sourceWidth / 2, -sourceHeight / 2);
122
+ }
123
+ ctx.restore();
124
+ // Apply all adjustments via pixel manipulation (Safari-compatible)
125
+ // For export, create a centered viewport with no offset
126
+ const exportViewport = {
127
+ zoom: 1,
128
+ offsetX: 0,
129
+ offsetY: 0,
130
+ scale: 1
131
+ };
132
+ applyAllAdjustments(canvas, img, exportViewport, adjustments, cropArea);
133
+ // Apply blur areas for export
134
+ if (blurAreas.length > 0) {
135
+ applyBlurAreas(canvas, img, exportViewport, blurAreas, cropArea);
136
+ }
137
+ // Apply stamps for export
138
+ if (stampAreas.length > 0) {
139
+ applyStamps(canvas, img, exportViewport, stampAreas, cropArea);
140
+ }
141
+ return canvas;
142
+ }
143
+ export function screenToImageCoords(screenX, screenY, canvas, img, viewport, transform) {
144
+ const rect = canvas.getBoundingClientRect();
145
+ // Convert screen coordinates to canvas coordinates
146
+ const scaleX = canvas.width / rect.width;
147
+ const scaleY = canvas.height / rect.height;
148
+ const canvasX = (screenX - rect.left) * scaleX;
149
+ const canvasY = (screenY - rect.top) * scaleY;
150
+ const centerX = canvas.width / 2;
151
+ const centerY = canvas.height / 2;
152
+ const totalScale = viewport.scale * viewport.zoom;
153
+ // Inverse transform
154
+ const x = (canvasX - centerX - viewport.offsetX) / totalScale + img.width / 2;
155
+ const y = (canvasY - centerY - viewport.offsetY) / totalScale + img.height / 2;
156
+ return { x, y };
157
+ }
158
+ export function imageToCanvasCoords(imageX, imageY, canvas, img, viewport) {
159
+ const centerX = canvas.width / 2;
160
+ const centerY = canvas.height / 2;
161
+ const totalScale = viewport.scale * viewport.zoom;
162
+ const x = (imageX - img.width / 2) * totalScale + centerX + viewport.offsetX;
163
+ const y = (imageY - img.height / 2) * totalScale + centerY + viewport.offsetY;
164
+ return { x, y };
165
+ }
166
+ // Deprecated: use imageToCanvasCoords instead
167
+ export function imageToScreenCoords(imageX, imageY, canvas, img, viewport) {
168
+ return imageToCanvasCoords(imageX, imageY, canvas, img, viewport);
169
+ }
170
+ /**
171
+ * Apply blur effects to specified areas of the canvas
172
+ * Uses pixel manipulation for Safari compatibility
173
+ */
174
+ export function applyBlurAreas(canvas, img, viewport, blurAreas, cropArea) {
175
+ const ctx = canvas.getContext('2d');
176
+ if (!ctx)
177
+ return;
178
+ const centerX = canvas.width / 2;
179
+ const centerY = canvas.height / 2;
180
+ const totalScale = viewport.scale * viewport.zoom;
181
+ // Create a temporary canvas to extract regions for blurring
182
+ const tempCanvas = document.createElement('canvas');
183
+ const tempCtx = tempCanvas.getContext('2d');
184
+ if (!tempCtx)
185
+ return;
186
+ blurAreas.forEach(blurArea => {
187
+ // Determine source dimensions based on crop
188
+ const sourceWidth = cropArea ? cropArea.width : img.width;
189
+ const sourceHeight = cropArea ? cropArea.height : img.height;
190
+ const offsetX = cropArea ? cropArea.x : 0;
191
+ const offsetY = cropArea ? cropArea.y : 0;
192
+ // Convert blur area to crop-relative coordinates
193
+ const relativeX = blurArea.x - offsetX;
194
+ const relativeY = blurArea.y - offsetY;
195
+ // Calculate blur area in canvas coordinates
196
+ const canvasBlurX = (relativeX - sourceWidth / 2) * totalScale + centerX + viewport.offsetX;
197
+ const canvasBlurY = (relativeY - sourceHeight / 2) * totalScale + centerY + viewport.offsetY;
198
+ const canvasBlurWidth = blurArea.width * totalScale;
199
+ const canvasBlurHeight = blurArea.height * totalScale;
200
+ // Calculate image bounds on canvas
201
+ const imgCanvasLeft = (0 - sourceWidth / 2) * totalScale + centerX + viewport.offsetX;
202
+ const imgCanvasTop = (0 - sourceHeight / 2) * totalScale + centerY + viewport.offsetY;
203
+ const imgCanvasRight = imgCanvasLeft + sourceWidth * totalScale;
204
+ const imgCanvasBottom = imgCanvasTop + sourceHeight * totalScale;
205
+ // Clip blur area to image bounds
206
+ const clippedX = Math.max(imgCanvasLeft, canvasBlurX);
207
+ const clippedY = Math.max(imgCanvasTop, canvasBlurY);
208
+ const clippedRight = Math.min(imgCanvasRight, canvasBlurX + canvasBlurWidth);
209
+ const clippedBottom = Math.min(imgCanvasBottom, canvasBlurY + canvasBlurHeight);
210
+ const clippedWidth = clippedRight - clippedX;
211
+ const clippedHeight = clippedBottom - clippedY;
212
+ if (clippedWidth <= 0 || clippedHeight <= 0)
213
+ return;
214
+ // Calculate blur radius in pixels
215
+ const imageBlurPx = (blurArea.blurStrength / 100) * 100;
216
+ const blurRadius = imageBlurPx * totalScale;
217
+ // Add padding for blur to work properly at edges
218
+ const padding = Math.ceil(blurRadius * 2);
219
+ const paddedX = Math.max(0, clippedX - padding);
220
+ const paddedY = Math.max(0, clippedY - padding);
221
+ const paddedRight = Math.min(canvas.width, clippedRight + padding);
222
+ const paddedBottom = Math.min(canvas.height, clippedBottom + padding);
223
+ const paddedWidth = paddedRight - paddedX;
224
+ const paddedHeight = paddedBottom - paddedY;
225
+ // Extract the padded region from the canvas
226
+ tempCanvas.width = paddedWidth;
227
+ tempCanvas.height = paddedHeight;
228
+ tempCtx.clearRect(0, 0, paddedWidth, paddedHeight);
229
+ tempCtx.drawImage(canvas, paddedX, paddedY, paddedWidth, paddedHeight, 0, 0, paddedWidth, paddedHeight);
230
+ // Apply blur to the temporary canvas
231
+ applyGaussianBlur(tempCanvas, 0, 0, paddedWidth, paddedHeight, blurRadius);
232
+ // Calculate the portion to draw back (excluding padding)
233
+ const srcX = clippedX - paddedX;
234
+ const srcY = clippedY - paddedY;
235
+ // Draw only the non-padded portion back to the main canvas
236
+ ctx.drawImage(tempCanvas, srcX, srcY, clippedWidth, clippedHeight, clippedX, clippedY, clippedWidth, clippedHeight);
237
+ });
238
+ }
239
+ /**
240
+ * Apply stamp decorations to the canvas
241
+ */
242
+ export function applyStamps(canvas, img, viewport, stampAreas, cropArea) {
243
+ const ctx = canvas.getContext('2d');
244
+ if (!ctx)
245
+ return;
246
+ const centerX = canvas.width / 2;
247
+ const centerY = canvas.height / 2;
248
+ const totalScale = viewport.scale * viewport.zoom;
249
+ stampAreas.forEach(stamp => {
250
+ // Determine source dimensions based on crop
251
+ const sourceWidth = cropArea ? cropArea.width : img.width;
252
+ const sourceHeight = cropArea ? cropArea.height : img.height;
253
+ const offsetX = cropArea ? cropArea.x : 0;
254
+ const offsetY = cropArea ? cropArea.y : 0;
255
+ // Convert stamp area to crop-relative coordinates
256
+ const relativeX = stamp.x - offsetX;
257
+ const relativeY = stamp.y - offsetY;
258
+ // Calculate stamp center in canvas coordinates
259
+ const canvasCenterX = (relativeX - sourceWidth / 2) * totalScale + centerX + viewport.offsetX;
260
+ const canvasCenterY = (relativeY - sourceHeight / 2) * totalScale + centerY + viewport.offsetY;
261
+ const canvasWidth = stamp.width * totalScale;
262
+ const canvasHeight = stamp.height * totalScale;
263
+ // Save context
264
+ ctx.save();
265
+ // Apply transformation
266
+ ctx.translate(canvasCenterX, canvasCenterY);
267
+ ctx.rotate((stamp.rotation || 0) * Math.PI / 180);
268
+ // Render stamp based on type
269
+ if (stamp.stampType === 'emoji') {
270
+ // Render emoji
271
+ ctx.font = `${canvasHeight}px Arial`;
272
+ ctx.textAlign = 'center';
273
+ ctx.textBaseline = 'middle';
274
+ ctx.fillText(stamp.stampContent, 0, 0);
275
+ }
276
+ else if (stamp.stampType === 'image' || stamp.stampType === 'svg') {
277
+ // Get image from cache
278
+ const stampImg = getStampImage(stamp.stampContent);
279
+ if (stampImg) {
280
+ ctx.drawImage(stampImg, -canvasWidth / 2, -canvasHeight / 2, canvasWidth, canvasHeight);
281
+ }
282
+ else {
283
+ // Draw placeholder while loading
284
+ ctx.fillStyle = '#ccc';
285
+ ctx.fillRect(-canvasWidth / 2, -canvasHeight / 2, canvasWidth, canvasHeight);
286
+ // Start loading if not already loading
287
+ preloadStampImage(stamp.stampContent).catch(console.error);
288
+ }
289
+ }
290
+ // Restore context
291
+ ctx.restore();
292
+ });
293
+ }
@@ -0,0 +1,18 @@
1
+ import type { FilterPreset, AdjustmentsState } from '../types';
2
+ /**
3
+ * Built-in filter presets
4
+ * Each filter is a combination of adjustment values
5
+ */
6
+ export declare const FILTER_PRESETS: FilterPreset[];
7
+ /**
8
+ * Get a filter preset by ID
9
+ */
10
+ export declare function getFilterPreset(id: string): FilterPreset | undefined;
11
+ /**
12
+ * Apply a filter preset to adjustments
13
+ */
14
+ export declare function applyFilterPreset(preset: FilterPreset, baseAdjustments?: AdjustmentsState): AdjustmentsState;
15
+ /**
16
+ * Check if current adjustments match a filter preset
17
+ */
18
+ export declare function matchesFilterPreset(adjustments: AdjustmentsState, preset: FilterPreset): boolean;
@@ -0,0 +1,114 @@
1
+ import { createDefaultAdjustments } from './adjustments';
2
+ /**
3
+ * Built-in filter presets
4
+ * Each filter is a combination of adjustment values
5
+ */
6
+ export const FILTER_PRESETS = [
7
+ {
8
+ id: 'none',
9
+ name: 'None',
10
+ adjustments: createDefaultAdjustments()
11
+ },
12
+ {
13
+ id: 'vivid',
14
+ name: 'Vivid',
15
+ adjustments: {
16
+ saturation: 40,
17
+ contrast: 20,
18
+ brightness: 5
19
+ }
20
+ },
21
+ {
22
+ id: 'sepia',
23
+ name: 'Sepia',
24
+ adjustments: {
25
+ sepia: 80,
26
+ brightness: -10,
27
+ contrast: -30,
28
+ highlights: -32,
29
+ shadows: 30,
30
+ vignette: -20
31
+ }
32
+ },
33
+ {
34
+ id: 'monochrome',
35
+ name: 'Monochrome',
36
+ adjustments: {
37
+ grayscale: 100,
38
+ contrast: 15
39
+ }
40
+ },
41
+ {
42
+ id: 'vintage',
43
+ name: 'Vintage',
44
+ adjustments: {
45
+ sepia: 50,
46
+ brightness: -15,
47
+ contrast: -10,
48
+ vignette: -40,
49
+ saturation: -20
50
+ }
51
+ },
52
+ {
53
+ id: 'warm',
54
+ name: 'Warm',
55
+ adjustments: {
56
+ sepia: -10,
57
+ saturation: 15,
58
+ brightness: 5,
59
+ exposure: 10,
60
+ hue: -10,
61
+ }
62
+ },
63
+ {
64
+ id: 'cool',
65
+ name: 'Cool',
66
+ adjustments: {
67
+ saturation: 10,
68
+ brightness: -5,
69
+ contrast: 10,
70
+ hue: 15,
71
+ }
72
+ },
73
+ {
74
+ id: 'film',
75
+ name: 'Film',
76
+ adjustments: {
77
+ contrast: 60,
78
+ highlights: -45,
79
+ shadows: -100,
80
+ saturation: 2,
81
+ vignette: -24,
82
+ }
83
+ }
84
+ ];
85
+ /**
86
+ * Get a filter preset by ID
87
+ */
88
+ export function getFilterPreset(id) {
89
+ return FILTER_PRESETS.find(preset => preset.id === id);
90
+ }
91
+ /**
92
+ * Apply a filter preset to adjustments
93
+ */
94
+ export function applyFilterPreset(preset, baseAdjustments = createDefaultAdjustments()) {
95
+ // Start with base adjustments or defaults
96
+ const result = { ...baseAdjustments };
97
+ // Apply preset values
98
+ Object.entries(preset.adjustments).forEach(([key, value]) => {
99
+ if (value !== undefined) {
100
+ result[key] = value;
101
+ }
102
+ });
103
+ return result;
104
+ }
105
+ /**
106
+ * Check if current adjustments match a filter preset
107
+ */
108
+ export function matchesFilterPreset(adjustments, preset) {
109
+ const presetAdjustments = applyFilterPreset(preset);
110
+ // Compare all adjustment values
111
+ return Object.keys(presetAdjustments).every(key => {
112
+ return adjustments[key] === presetAdjustments[key];
113
+ });
114
+ }
@@ -0,0 +1,15 @@
1
+ import type { HistorySnapshot, EditorHistory } from '../types';
2
+ export declare const MAX_HISTORY_SIZE = 50;
3
+ export declare function createEmptyHistory(): EditorHistory;
4
+ export declare function createSnapshot(cropArea: any, transform: any, adjustments: any, viewport: any, blurAreas?: any[], stampAreas?: any[]): HistorySnapshot;
5
+ export declare function addToHistory(history: EditorHistory, snapshot: HistorySnapshot): EditorHistory;
6
+ export declare function undo(history: EditorHistory): {
7
+ history: EditorHistory;
8
+ snapshot: HistorySnapshot | null;
9
+ };
10
+ export declare function redo(history: EditorHistory): {
11
+ history: EditorHistory;
12
+ snapshot: HistorySnapshot | null;
13
+ };
14
+ export declare function canUndo(history: EditorHistory): boolean;
15
+ export declare function canRedo(history: EditorHistory): boolean;
@@ -0,0 +1,67 @@
1
+ // Maximum number of history states to keep
2
+ export const MAX_HISTORY_SIZE = 50;
3
+ export function createEmptyHistory() {
4
+ return {
5
+ past: [],
6
+ present: null,
7
+ future: []
8
+ };
9
+ }
10
+ export function createSnapshot(cropArea, transform, adjustments, viewport, blurAreas = [], stampAreas = []) {
11
+ return {
12
+ cropArea: cropArea ? { ...cropArea } : null,
13
+ transform: { ...transform },
14
+ adjustments: { ...adjustments },
15
+ viewport: { ...viewport },
16
+ blurAreas: blurAreas.map(area => ({ ...area })),
17
+ stampAreas: stampAreas.map(area => ({ ...area }))
18
+ };
19
+ }
20
+ export function addToHistory(history, snapshot) {
21
+ const newPast = history.present
22
+ ? [...history.past, history.present]
23
+ : history.past;
24
+ // Limit history size
25
+ const limitedPast = newPast.slice(-MAX_HISTORY_SIZE);
26
+ return {
27
+ past: limitedPast,
28
+ present: snapshot,
29
+ future: [] // Clear future when adding new state
30
+ };
31
+ }
32
+ export function undo(history) {
33
+ if (history.past.length === 0 || !history.present) {
34
+ return { history, snapshot: null };
35
+ }
36
+ const previous = history.past[history.past.length - 1];
37
+ const newPast = history.past.slice(0, -1);
38
+ return {
39
+ history: {
40
+ past: newPast,
41
+ present: previous,
42
+ future: [history.present, ...history.future]
43
+ },
44
+ snapshot: previous
45
+ };
46
+ }
47
+ export function redo(history) {
48
+ if (history.future.length === 0 || !history.present) {
49
+ return { history, snapshot: null };
50
+ }
51
+ const next = history.future[0];
52
+ const newFuture = history.future.slice(1);
53
+ return {
54
+ history: {
55
+ past: [...history.past, history.present],
56
+ present: next,
57
+ future: newFuture
58
+ },
59
+ snapshot: next
60
+ };
61
+ }
62
+ export function canUndo(history) {
63
+ return history.past.length > 0;
64
+ }
65
+ export function canRedo(history) {
66
+ return history.future.length > 0;
67
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokimeki-image-editor",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "A image editor for svelte.",
5
5
  "type": "module",
6
6
  "license": "MIT",