@lemonadejs/cropper 5.0.1 → 6.0.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.
- package/README.md +335 -1
- package/dist/index.d.ts +119 -0
- package/dist/index.js +138 -175
- package/package.json +23 -24
package/README.md
CHANGED
|
@@ -1 +1,335 @@
|
|
|
1
|
-
#
|
|
1
|
+
# JavaScript Image Cropper - LemonadeJS Cropper Plugin
|
|
2
|
+
|
|
3
|
+
A lightweight, powerful **JavaScript image cropper** and photo editor component built with [LemonadeJS](https://lemonadejs.com). Crop, resize, rotate, and adjust images directly in your web browser with this modern, reactive image editing solution.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
|
|
7
|
+
- **Image Cropping**: Intuitive drag-and-drop interface for precise image cropping
|
|
8
|
+
- **Image Rotation**: Rotate images with smooth, real-time preview
|
|
9
|
+
- **Zoom Control**: Fine-grained zoom functionality from 0.1x to 5.45x
|
|
10
|
+
- **Image Adjustments**: Brightness and contrast controls for professional results
|
|
11
|
+
- **Responsive Design**: Automatically adapts to mobile and desktop screens
|
|
12
|
+
- **Modal Interface**: Clean, user-friendly modal dialog for editing
|
|
13
|
+
- **Blob Output**: Generates cropped images as blobs for easy upload
|
|
14
|
+
- **Original Image Preservation**: Optionally keep the original image alongside the cropped version
|
|
15
|
+
- **TypeScript Support**: Full TypeScript definitions included
|
|
16
|
+
- **Framework Integration**: Works with vanilla JavaScript, React, Vue, and any modern framework
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
### NPM Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @lemonadejs/cropper
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### CDN Installation
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<!-- LemonadeJS Core -->
|
|
31
|
+
<script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
|
|
32
|
+
|
|
33
|
+
<!-- jSuites Cropper (dependency) -->
|
|
34
|
+
<script src="https://cdn.jsdelivr.net/npm/@jsuites/cropper/cropper.min.js"></script>
|
|
35
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jsuites/cropper/cropper.min.css" />
|
|
36
|
+
|
|
37
|
+
<!-- LemonadeJS Cropper Plugin -->
|
|
38
|
+
<script src="https://cdn.jsdelivr.net/npm/@lemonadejs/cropper/dist/index.js"></script>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start Example
|
|
42
|
+
|
|
43
|
+
### Basic Image Cropper
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<!DOCTYPE html>
|
|
47
|
+
<html>
|
|
48
|
+
<head>
|
|
49
|
+
<script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
|
|
50
|
+
<script src="https://cdn.jsdelivr.net/npm/@jsuites/cropper/cropper.min.js"></script>
|
|
51
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jsuites/cropper/cropper.min.css" />
|
|
52
|
+
<script src="https://cdn.jsdelivr.net/npm/@lemonadejs/cropper/dist/index.js"></script>
|
|
53
|
+
</head>
|
|
54
|
+
<body>
|
|
55
|
+
<div id="root"></div>
|
|
56
|
+
|
|
57
|
+
<script>
|
|
58
|
+
const root = document.getElementById('root');
|
|
59
|
+
const cropper = Cropper(root, {
|
|
60
|
+
width: 300,
|
|
61
|
+
height: 240
|
|
62
|
+
});
|
|
63
|
+
</script>
|
|
64
|
+
</body>
|
|
65
|
+
</html>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Using with LemonadeJS Component
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
import lemonade from 'lemonadejs';
|
|
72
|
+
import Cropper from '@lemonadejs/cropper';
|
|
73
|
+
|
|
74
|
+
export default function MyApp() {
|
|
75
|
+
const self = this;
|
|
76
|
+
|
|
77
|
+
return `<div>
|
|
78
|
+
<h1>Profile Picture Editor</h1>
|
|
79
|
+
<Cropper
|
|
80
|
+
width="400"
|
|
81
|
+
height="300"
|
|
82
|
+
:ref="self.cropper"
|
|
83
|
+
original="true" />
|
|
84
|
+
<button onclick="self.cropper.open()">Edit Photo</button>
|
|
85
|
+
</div>`;
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### React Integration
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
import React, { useRef, useEffect } from 'react';
|
|
93
|
+
import Cropper from '@lemonadejs/cropper';
|
|
94
|
+
|
|
95
|
+
function PhotoEditor() {
|
|
96
|
+
const cropperRef = useRef(null);
|
|
97
|
+
const containerRef = useRef(null);
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (containerRef.current) {
|
|
101
|
+
cropperRef.current = Cropper(containerRef.current, {
|
|
102
|
+
width: 400,
|
|
103
|
+
height: 300,
|
|
104
|
+
original: true
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return () => {
|
|
109
|
+
// Cleanup if needed
|
|
110
|
+
};
|
|
111
|
+
}, []);
|
|
112
|
+
|
|
113
|
+
const handleGetImage = () => {
|
|
114
|
+
if (cropperRef.current) {
|
|
115
|
+
const imageData = cropperRef.current.getValue();
|
|
116
|
+
console.log('Image data:', imageData);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div>
|
|
122
|
+
<div ref={containerRef}></div>
|
|
123
|
+
<button onClick={handleGetImage}>Get Image Data</button>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export default PhotoEditor;
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Advanced Configuration
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
const cropper = Cropper(document.getElementById('root'), {
|
|
135
|
+
width: 500, // Crop area width
|
|
136
|
+
height: 400, // Crop area height
|
|
137
|
+
original: true, // Preserve original image
|
|
138
|
+
name: 'profile_pic' // Form field name
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Set an image programmatically
|
|
142
|
+
cropper.setValue({
|
|
143
|
+
file: 'https://example.com/image.jpg'
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Get cropped image data
|
|
147
|
+
const imageData = cropper.getValue();
|
|
148
|
+
console.log(imageData);
|
|
149
|
+
// Output: [{ file: 'blob:...', content: 'data:image/png;base64,...', extension: 'png' }]
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Usage Examples
|
|
153
|
+
|
|
154
|
+
### Profile Picture Upload
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
const profileCropper = Cropper(document.getElementById('profile-editor'), {
|
|
158
|
+
width: 200,
|
|
159
|
+
height: 200,
|
|
160
|
+
original: true
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// After user crops the image
|
|
164
|
+
async function uploadProfilePicture() {
|
|
165
|
+
const data = profileCropper.getValue();
|
|
166
|
+
|
|
167
|
+
if (data && data[0]) {
|
|
168
|
+
const formData = new FormData();
|
|
169
|
+
const response = await fetch(data[0].content);
|
|
170
|
+
const blob = await response.blob();
|
|
171
|
+
formData.append('profile_picture', blob, 'profile.png');
|
|
172
|
+
|
|
173
|
+
// Upload to server
|
|
174
|
+
await fetch('/api/upload', {
|
|
175
|
+
method: 'POST',
|
|
176
|
+
body: formData
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### E-commerce Product Image Editor
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
const productCropper = Cropper(document.getElementById('product-images'), {
|
|
186
|
+
width: 800,
|
|
187
|
+
height: 600,
|
|
188
|
+
original: true, // Keep original for zoom functionality
|
|
189
|
+
name: 'product_image'
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Handle multiple product images
|
|
193
|
+
function addProductImage(imageUrl) {
|
|
194
|
+
productCropper.setValue({
|
|
195
|
+
file: imageUrl,
|
|
196
|
+
original: imageUrl
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Responsive Mobile-Friendly Cropper
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
// Automatically adjusts for mobile devices
|
|
205
|
+
const responsiveCropper = Cropper(document.getElementById('mobile-editor'), {
|
|
206
|
+
width: window.innerWidth < 640 ? window.innerWidth - 40 : 600,
|
|
207
|
+
height: window.innerWidth < 640 ? window.innerWidth - 40 : 450
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// The cropper automatically adapts its interface for screens < 800px
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## API Reference
|
|
214
|
+
|
|
215
|
+
### Options
|
|
216
|
+
|
|
217
|
+
| Property | Type | Default | Description |
|
|
218
|
+
|------------|-----------|-----------|----------------------------------------|
|
|
219
|
+
| `width` | number | 300 | Width of the crop area in pixels |
|
|
220
|
+
| `height` | number | 240 | Height of the crop area in pixels |
|
|
221
|
+
| `original` | boolean | false | Whether to preserve the original image |
|
|
222
|
+
| `name` | string | undefined | Name attribute for form integration |
|
|
223
|
+
| `value` | ImageData | null | Initial image data |
|
|
224
|
+
|
|
225
|
+
### Methods
|
|
226
|
+
|
|
227
|
+
#### getValue()
|
|
228
|
+
Retrieve the current cropped image data and metadata.
|
|
229
|
+
|
|
230
|
+
```javascript
|
|
231
|
+
const data = cropper.getValue();
|
|
232
|
+
// Returns: { file: 'blob:...', content: 'data:image/...', extension: 'png' }
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Returns:** `ImageData` - Object containing image data with file URL, content, and extension
|
|
236
|
+
|
|
237
|
+
#### setValue(data)
|
|
238
|
+
Set or update the image in the cropper.
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
// Set image with URL
|
|
242
|
+
cropper.setValue({
|
|
243
|
+
file: 'path/to/image.jpg',
|
|
244
|
+
original: 'path/to/original.jpg'
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Set image with string
|
|
248
|
+
cropper.setValue('https://example.com/image.jpg');
|
|
249
|
+
|
|
250
|
+
// Clear the image
|
|
251
|
+
cropper.setValue(null);
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Parameters:**
|
|
255
|
+
- `data` (string | ImageData | null): Image URL, data object, array of data objects, or null to clear
|
|
256
|
+
|
|
257
|
+
#### open()
|
|
258
|
+
Programmatically open the cropper modal interface.
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
cropper.open();
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
#### uploadPhoto()
|
|
265
|
+
Launch the file picker modal to allow users to select a new photo.
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
// Trigger file upload dialog
|
|
269
|
+
document.querySelector('.upload-btn').addEventListener('click', () => {
|
|
270
|
+
// Note: This is handled internally by the cropper UI
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
#### deletePhoto()
|
|
275
|
+
Remove the current image from the cropper container.
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
// Note: This method is available through the context menu and UI controls
|
|
279
|
+
// The cropper automatically resets when the image is deleted
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
#### setControls(state)
|
|
283
|
+
Enable or disable the editing controls (zoom, rotate, brightness, contrast).
|
|
284
|
+
|
|
285
|
+
```javascript
|
|
286
|
+
cropper.setControls(false); // Disable all controls
|
|
287
|
+
cropper.setControls(true); // Enable all controls
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Parameters:**
|
|
291
|
+
- `state` (boolean): True to enable, false to disable
|
|
292
|
+
|
|
293
|
+
### Properties
|
|
294
|
+
|
|
295
|
+
| Property | Type | Description |
|
|
296
|
+
|---------------|-------------|-----------------------------------------------|
|
|
297
|
+
| `brightness` | number | Current brightness adjustment value (-1 to 1) |
|
|
298
|
+
| `contrast` | number | Current contrast adjustment value (-1 to 1) |
|
|
299
|
+
| `greyscale` | number | Current greyscale value (0 to 1) |
|
|
300
|
+
| `cropperArea` | HTMLElement | Reference to the cropper area DOM element |
|
|
301
|
+
|
|
302
|
+
### ImageData Interface
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
interface ImageData {
|
|
306
|
+
file: string; // URL or blob URL to the image
|
|
307
|
+
content?: string; // Base64 or data URL content
|
|
308
|
+
extension?: string; // File extension (e.g., 'jpg', 'png')
|
|
309
|
+
original?: string; // Original image URL (if preserved)
|
|
310
|
+
guid?: string;
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Related Projects
|
|
315
|
+
|
|
316
|
+
- **[LemonadeJS](https://lemonadejs.com)** - Reactive micro JavaScript library
|
|
317
|
+
- **[Jspreadsheet](https://jspreadsheet.com)** - JavaScript data grid and spreadsheet component
|
|
318
|
+
- **[CalendarJS](https://calendarjs.com)** - JavaScript calendar, schedule and timeline components
|
|
319
|
+
- **[jSuites](https://jsuites.net)** - JavaScript plugins and web components collection
|
|
320
|
+
|
|
321
|
+
## License
|
|
322
|
+
|
|
323
|
+
MIT License
|
|
324
|
+
|
|
325
|
+
## Support & Community
|
|
326
|
+
|
|
327
|
+
- **Documentation**: [https://lemonadejs.com/plugins/cropper](https://lemonadejs.com/plugins/cropper)
|
|
328
|
+
- **GitHub Issues**: Report bugs and request features
|
|
329
|
+
- **Community**: Join our community for support and discussions
|
|
330
|
+
- **Website**: [https://lemonadejs.com](https://lemonadejs.com)
|
|
331
|
+
|
|
332
|
+
## Contributing
|
|
333
|
+
|
|
334
|
+
Contributions are welcome! Please feel free to submit pull requests or open issues.
|
|
335
|
+
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LemonadeJS Cropper Component
|
|
3
|
+
* Image cropper with crop, zoom, rotate, and adjustment controls
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
declare namespace Cropper {
|
|
7
|
+
/**
|
|
8
|
+
* Data structure for cropped image
|
|
9
|
+
*/
|
|
10
|
+
interface ImageData {
|
|
11
|
+
/** URL or blob URL to the image file */
|
|
12
|
+
file: string;
|
|
13
|
+
/** Base64 or data URL content of the image */
|
|
14
|
+
content?: string;
|
|
15
|
+
/** File extension (e.g., 'jpg', 'png') */
|
|
16
|
+
extension?: string;
|
|
17
|
+
/** Original image URL (if original option is enabled) */
|
|
18
|
+
original?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Configuration options for the Cropper component
|
|
23
|
+
*/
|
|
24
|
+
interface Options {
|
|
25
|
+
/** Name attribute for the component (useful for forms) */
|
|
26
|
+
name?: string;
|
|
27
|
+
|
|
28
|
+
/** Initial value - image data */
|
|
29
|
+
value?: ImageData[] | null;
|
|
30
|
+
|
|
31
|
+
/** Width of the crop area in pixels (default: 300) */
|
|
32
|
+
width?: number;
|
|
33
|
+
|
|
34
|
+
/** Height of the crop area in pixels (default: 240) */
|
|
35
|
+
height?: number;
|
|
36
|
+
|
|
37
|
+
/** Whether to keep the original image (default: false) */
|
|
38
|
+
original?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Cropper component instance
|
|
43
|
+
*/
|
|
44
|
+
interface Instance {
|
|
45
|
+
/** Get the current cropped image data */
|
|
46
|
+
getValue(): ImageData[] | null;
|
|
47
|
+
|
|
48
|
+
/** Set or update the image data */
|
|
49
|
+
setValue(data: ImageData | ImageData[] | string | null): void;
|
|
50
|
+
|
|
51
|
+
/** Open the cropper modal */
|
|
52
|
+
open(): void;
|
|
53
|
+
|
|
54
|
+
/** Enable or disable cropper controls */
|
|
55
|
+
setControls(state: boolean): void;
|
|
56
|
+
|
|
57
|
+
/** Current brightness adjustment value */
|
|
58
|
+
brightness: number;
|
|
59
|
+
|
|
60
|
+
/** Current contrast adjustment value */
|
|
61
|
+
contrast: number;
|
|
62
|
+
|
|
63
|
+
/** Current greyscale adjustment value */
|
|
64
|
+
greyscale: number;
|
|
65
|
+
|
|
66
|
+
/** Reference to the cropper area element */
|
|
67
|
+
cropperArea: HTMLElement | null;
|
|
68
|
+
|
|
69
|
+
/** Name attribute */
|
|
70
|
+
name?: string;
|
|
71
|
+
|
|
72
|
+
/** Current value */
|
|
73
|
+
value: ImageData[] | null;
|
|
74
|
+
|
|
75
|
+
/** Width setting */
|
|
76
|
+
width?: number;
|
|
77
|
+
|
|
78
|
+
/** Height setting */
|
|
79
|
+
height?: number;
|
|
80
|
+
|
|
81
|
+
/** Original image setting */
|
|
82
|
+
original?: boolean;
|
|
83
|
+
|
|
84
|
+
/** Reference to the root DOM element */
|
|
85
|
+
el: HTMLElement;
|
|
86
|
+
|
|
87
|
+
/** Reference to the image container element */
|
|
88
|
+
image: HTMLElement;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Create a Cropper component instance
|
|
94
|
+
*
|
|
95
|
+
* @param root - DOM element or container where the cropper will be rendered
|
|
96
|
+
* @param options - Configuration options for the cropper
|
|
97
|
+
* @returns Cropper instance
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```javascript
|
|
101
|
+
* // Create a cropper with custom dimensions
|
|
102
|
+
* const cropper = Cropper(document.getElementById('container'), {
|
|
103
|
+
* width: 400,
|
|
104
|
+
* height: 300,
|
|
105
|
+
* original: true
|
|
106
|
+
* });
|
|
107
|
+
*
|
|
108
|
+
* // Set an image
|
|
109
|
+
* cropper.setValue({
|
|
110
|
+
* file: 'path/to/image.jpg'
|
|
111
|
+
* });
|
|
112
|
+
*
|
|
113
|
+
* // Get cropped image data
|
|
114
|
+
* const data = cropper.getValue();
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
declare function Cropper(root: HTMLElement, options?: Cropper.Options): Cropper.Instance;
|
|
118
|
+
|
|
119
|
+
export default Cropper;
|
package/dist/index.js
CHANGED
|
@@ -16,16 +16,7 @@ if (!studio && typeof (require) === 'function') {
|
|
|
16
16
|
var studio = require('@lemonadejs/studio');
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
if (typeof(jSuites) == 'undefined') {
|
|
21
|
-
if (typeof(require) === 'function') {
|
|
22
|
-
var jSuites = require('jsuites');
|
|
23
|
-
} else if (window.jSuites) {
|
|
24
|
-
var jSuites = window.jSuites;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (typeof(cropper) == 'undefined') {
|
|
19
|
+
if (typeof(cropper) === 'undefined') {
|
|
29
20
|
if (typeof(require) === 'function') {
|
|
30
21
|
var Crop = require('@jsuites/cropper');
|
|
31
22
|
} else if (window.cropper) {
|
|
@@ -43,34 +34,55 @@ if (typeof(cropper) == 'undefined') {
|
|
|
43
34
|
|
|
44
35
|
'use strict';
|
|
45
36
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
37
|
+
const translate = function (t) {
|
|
38
|
+
if (typeof document !== 'undefined' && document.dictionary) {
|
|
39
|
+
return document.dictionary[t] || t;
|
|
40
|
+
} else {
|
|
41
|
+
return t;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const getWindowWidth = function() {
|
|
46
|
+
let w = window,
|
|
47
|
+
d = document,
|
|
48
|
+
e = d.documentElement,
|
|
49
|
+
g = d.getElementsByTagName('body')[0],
|
|
50
|
+
x = w.innerWidth || e.clientWidth || g.clientWidth;
|
|
51
|
+
return x;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const Cropper = function(children, { onload, onchange, track }) {
|
|
55
|
+
const self = this;
|
|
56
|
+
const original = self.original ? 1 : 0;
|
|
57
|
+
const width = self.width || 300;
|
|
58
|
+
const height = self.height || 240;
|
|
52
59
|
let crop = null;
|
|
53
|
-
|
|
60
|
+
|
|
61
|
+
const menu = [
|
|
62
|
+
{
|
|
63
|
+
title: translate('Change image'),
|
|
64
|
+
icon: 'edit',
|
|
65
|
+
onclick: () => {
|
|
66
|
+
self.open();
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
title: translate('Delete image'),
|
|
71
|
+
icon: 'delete',
|
|
72
|
+
shortcut: 'DELETE',
|
|
73
|
+
onclick: (e) => {
|
|
74
|
+
deletePhoto();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
]
|
|
54
78
|
|
|
55
79
|
self.cropperArea = null;
|
|
56
80
|
self.brightness = 0;
|
|
57
81
|
self.contrast = 0;
|
|
58
82
|
self.greyscale = 0;
|
|
59
83
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
modal = jSuites.modal(o, {
|
|
63
|
-
closed: true,
|
|
64
|
-
width: '800px',
|
|
65
|
-
height: '680px',
|
|
66
|
-
title: 'Photo Upload',
|
|
67
|
-
padding: '0',
|
|
68
|
-
icon: 'photo'
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
self.createCropper = function(o) {
|
|
73
|
-
let area = jSuites.getWindowWidth();
|
|
84
|
+
self.createCropper = (o) => {
|
|
85
|
+
const area = getWindowWidth();
|
|
74
86
|
let a;
|
|
75
87
|
let c;
|
|
76
88
|
if (area < 800) {
|
|
@@ -82,9 +94,9 @@ if (typeof(cropper) == 'undefined') {
|
|
|
82
94
|
}
|
|
83
95
|
crop = Crop(o, {
|
|
84
96
|
area: a,
|
|
85
|
-
crop: c
|
|
97
|
+
crop: c,
|
|
86
98
|
allowResize: false,
|
|
87
|
-
onchange:
|
|
99
|
+
onchange: (el, image) => {
|
|
88
100
|
if (image) {
|
|
89
101
|
self.setControls(true);
|
|
90
102
|
}
|
|
@@ -92,37 +104,6 @@ if (typeof(cropper) == 'undefined') {
|
|
|
92
104
|
});
|
|
93
105
|
}
|
|
94
106
|
|
|
95
|
-
self.createMenu = function(o) {
|
|
96
|
-
// Start contextmenu component
|
|
97
|
-
menu = jSuites.contextmenu(o, {
|
|
98
|
-
onclick: function(a,b) {
|
|
99
|
-
a.close();
|
|
100
|
-
b.stopPropagation();
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Controls
|
|
106
|
-
self.createControls = function(o) {
|
|
107
|
-
let tabs = jSuites.tabs(o.children[0], {
|
|
108
|
-
data: [{
|
|
109
|
-
title: 'Crop',
|
|
110
|
-
icon: 'crop',
|
|
111
|
-
width: '100px',
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
title:'Adjusts',
|
|
115
|
-
icon: 'image',
|
|
116
|
-
width: '100px',
|
|
117
|
-
}],
|
|
118
|
-
padding:'10px',
|
|
119
|
-
animation: true,
|
|
120
|
-
position: 'bottom',
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
tabs.content.style.backgroundColor = '#eee';
|
|
124
|
-
}
|
|
125
|
-
|
|
126
107
|
const updateZoom = (e) => {
|
|
127
108
|
crop.zoom(e.target.value);
|
|
128
109
|
}
|
|
@@ -142,63 +123,52 @@ if (typeof(cropper) == 'undefined') {
|
|
|
142
123
|
const updatePhoto = () => {
|
|
143
124
|
// Checks if cropper container is editable
|
|
144
125
|
if (self.cropperArea.classList.contains('jcrop_edition')) {
|
|
145
|
-
self.image.innerHTML = '';
|
|
146
126
|
// Create image with metadata
|
|
147
|
-
|
|
127
|
+
const newImage = crop.getCroppedImage();
|
|
148
128
|
// Callback for the blob
|
|
149
|
-
|
|
129
|
+
const createImage = (b) => {
|
|
150
130
|
// Transform to blob
|
|
151
|
-
|
|
131
|
+
const filename = window.URL.createObjectURL(b);
|
|
152
132
|
// Set upload information
|
|
153
|
-
|
|
133
|
+
const data = {
|
|
154
134
|
file: filename,
|
|
155
135
|
content: newImage.src,
|
|
156
136
|
extension: newImage.content.extension,
|
|
157
137
|
};
|
|
158
138
|
// Upload original image
|
|
159
|
-
if (original
|
|
139
|
+
if (original === true) {
|
|
160
140
|
data.original = crop.getImage().src;
|
|
161
141
|
}
|
|
162
|
-
// Update file to blob
|
|
163
|
-
newImage.src = filename;
|
|
164
|
-
// Integration with jSuites.form
|
|
165
|
-
if (self.name) {
|
|
166
|
-
newImage.classList.remove('jfile');
|
|
167
|
-
}
|
|
168
142
|
// Value
|
|
169
|
-
self.value =
|
|
170
|
-
// Append new image
|
|
171
|
-
self.image.appendChild(newImage);
|
|
143
|
+
self.value = data;
|
|
172
144
|
}
|
|
173
145
|
// Create image
|
|
174
146
|
crop.getCroppedAsBlob(createImage);
|
|
175
147
|
// Close the modal
|
|
176
|
-
setTimeout(
|
|
177
|
-
modal.close();
|
|
148
|
+
setTimeout(() => {
|
|
149
|
+
self.modal.close();
|
|
178
150
|
});
|
|
179
151
|
}
|
|
180
152
|
}
|
|
181
153
|
|
|
182
154
|
const uploadPhoto = () => {
|
|
183
|
-
|
|
155
|
+
crop.getFileInput().click();
|
|
184
156
|
}
|
|
185
157
|
|
|
186
|
-
const deletePhoto =
|
|
158
|
+
const deletePhoto = () => {
|
|
187
159
|
if (self.image) {
|
|
188
160
|
// Reset photo from crop
|
|
189
161
|
crop.reset();
|
|
190
162
|
// Disable controls
|
|
191
163
|
self.setControls(false);
|
|
192
|
-
// Reset from container
|
|
193
|
-
self.image.innerHTML = '';
|
|
194
164
|
// Reset container
|
|
195
|
-
self.value =
|
|
165
|
+
self.value = '';
|
|
196
166
|
}
|
|
197
167
|
}
|
|
198
168
|
|
|
199
|
-
self.setControls =
|
|
200
|
-
|
|
201
|
-
if (state
|
|
169
|
+
self.setControls = (state) => {
|
|
170
|
+
const controls = self.el.querySelectorAll('input.controls');
|
|
171
|
+
if (state === false) {
|
|
202
172
|
for (let i = 0; i < controls.length; i++) {
|
|
203
173
|
controls[i].setAttribute('disabled', 'disabled');
|
|
204
174
|
}
|
|
@@ -215,149 +185,142 @@ if (typeof(cropper) == 'undefined') {
|
|
|
215
185
|
}
|
|
216
186
|
}
|
|
217
187
|
|
|
218
|
-
self.getValue =
|
|
188
|
+
self.getValue = () => {
|
|
219
189
|
return self.value;
|
|
220
190
|
}
|
|
221
191
|
|
|
222
|
-
self.setValue =
|
|
223
|
-
if (!
|
|
192
|
+
self.setValue = (data) => {
|
|
193
|
+
if (!data) {
|
|
224
194
|
// Reset container
|
|
225
195
|
deletePhoto();
|
|
226
196
|
} else {
|
|
227
|
-
if (typeof(data)
|
|
197
|
+
if (typeof(data) === 'string') {
|
|
228
198
|
data = {
|
|
229
199
|
file: data
|
|
230
200
|
}
|
|
231
201
|
}
|
|
232
202
|
|
|
233
|
-
if (data.file) {
|
|
234
|
-
let img = document.createElement('img');
|
|
235
|
-
img.setAttribute('src', data.file);
|
|
236
|
-
img.setAttribute('tabindex', -1);
|
|
237
|
-
self.image.innerHTML = '';
|
|
238
|
-
self.image.appendChild(img);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
203
|
if (data.original) {
|
|
242
204
|
crop.addFromFile(data.original);
|
|
243
205
|
}
|
|
244
206
|
|
|
245
|
-
self.value =
|
|
207
|
+
self.value = data;
|
|
246
208
|
}
|
|
247
209
|
}
|
|
248
210
|
|
|
249
|
-
self.open =
|
|
250
|
-
if (
|
|
251
|
-
modal.open();
|
|
211
|
+
self.open = () => {
|
|
212
|
+
if (self.modal.isClosed()) {
|
|
213
|
+
self.modal.open();
|
|
252
214
|
}
|
|
253
215
|
}
|
|
254
216
|
|
|
255
|
-
|
|
217
|
+
onload(() => {
|
|
256
218
|
self.image.style.maxWidth = self.width + 'px';
|
|
257
219
|
self.image.style.maxHeight = self.height + 'px';
|
|
258
220
|
|
|
259
221
|
// Onclick event
|
|
260
|
-
self.
|
|
222
|
+
self.image.addEventListener('mousedown', (e) => {
|
|
261
223
|
let mouseButton;
|
|
262
|
-
|
|
263
|
-
if (
|
|
264
|
-
mouseButton =
|
|
265
|
-
} else if (
|
|
266
|
-
mouseButton =
|
|
224
|
+
const event = e || window.event;
|
|
225
|
+
if (event.buttons) {
|
|
226
|
+
mouseButton = event.buttons;
|
|
227
|
+
} else if (event.button) {
|
|
228
|
+
mouseButton = event.button;
|
|
267
229
|
} else {
|
|
268
|
-
mouseButton =
|
|
230
|
+
mouseButton = event.which;
|
|
269
231
|
}
|
|
270
232
|
|
|
271
|
-
if (mouseButton
|
|
233
|
+
if (mouseButton === 1) {
|
|
272
234
|
self.open();
|
|
273
235
|
} else {
|
|
274
|
-
if (
|
|
275
|
-
|
|
236
|
+
if (event.target.tagName === 'IMG') {
|
|
237
|
+
event.target.focus();
|
|
276
238
|
}
|
|
277
239
|
}
|
|
278
240
|
});
|
|
279
241
|
|
|
280
|
-
self.el.addEventListener('
|
|
281
|
-
if (e.target.tagName
|
|
282
|
-
menu.open(e, [
|
|
283
|
-
{
|
|
284
|
-
title: jSuites.translate('Change image'),
|
|
285
|
-
icon: 'edit',
|
|
286
|
-
onclick: function() {
|
|
287
|
-
self.open();
|
|
288
|
-
}
|
|
289
|
-
},
|
|
290
|
-
{
|
|
291
|
-
title: jSuites.translate('Delete image'),
|
|
292
|
-
icon: 'delete',
|
|
293
|
-
shortcut: 'DELETE',
|
|
294
|
-
onclick: function() {
|
|
295
|
-
e.target.remove();
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
]);
|
|
299
|
-
e.preventDefault();
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
self.el.addEventListener('onkeydown', function(e) {
|
|
304
|
-
if (e.key == 'Delete' && e.target.tagName == 'IMG') {
|
|
242
|
+
self.el.addEventListener('onkeydown', (e) => {
|
|
243
|
+
if (e.key === 'Delete' && e.target.tagName === 'IMG') {
|
|
305
244
|
deletePhoto();
|
|
306
245
|
}
|
|
307
|
-
})
|
|
246
|
+
});
|
|
308
247
|
|
|
309
|
-
self.el.val =
|
|
248
|
+
self.el.val = (v) => {
|
|
310
249
|
if (v === undefined) {
|
|
311
250
|
return self.getValue();
|
|
312
251
|
} else {
|
|
313
252
|
self.setValue(v);
|
|
314
253
|
}
|
|
315
254
|
}
|
|
255
|
+
});
|
|
316
256
|
|
|
317
|
-
|
|
257
|
+
onchange((prop) => {
|
|
258
|
+
let image = self.value;
|
|
259
|
+
let filename = null;
|
|
260
|
+
|
|
261
|
+
if (Array.isArray(self.value)) {
|
|
262
|
+
image = self.value[0];
|
|
263
|
+
}
|
|
264
|
+
const img = document.createElement('img');
|
|
265
|
+
img.setAttribute('tabindex', -1);
|
|
266
|
+
self.image.textContent = '';
|
|
267
|
+
// Integration with
|
|
268
|
+
if (self.name) {
|
|
269
|
+
img.classList.remove('jfile');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (image) {
|
|
273
|
+
if (typeof image === 'string') {
|
|
274
|
+
filename = image;
|
|
275
|
+
} else if (typeof image === 'object') {
|
|
276
|
+
filename = image.file;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (filename) {
|
|
281
|
+
if (typeof self.onbeforeloadimage === 'function') {
|
|
282
|
+
let ret = self.onbeforeloadimage(self, filename, img);
|
|
283
|
+
if (ret) {
|
|
284
|
+
filename = ret;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
img.setAttribute('src', filename);
|
|
288
|
+
self.image.appendChild(img);
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
track('value');
|
|
318
293
|
|
|
319
294
|
// Template
|
|
320
|
-
return render => render `<div name="{{self.name}}"
|
|
295
|
+
return render => render `<div name="{{self.name}}">
|
|
321
296
|
<div :ref='this.image' class="jphoto jcropper"></div>
|
|
322
|
-
<
|
|
297
|
+
<lm-modal draggable="true" closable="true" closed="true" width="800" height="580" title="Photo Upload" icon="photo" :ref="self.modal">
|
|
323
298
|
<div :ready='self.createCropper' :ref='self.cropperArea'></div>
|
|
324
|
-
<div
|
|
325
|
-
<div
|
|
326
|
-
<
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
<div role='content' style='background-color: #ccc;'>
|
|
331
|
-
<div>
|
|
332
|
-
<div class="lm-center lm-row">
|
|
333
|
-
<label class="lm-f1 lm-p6"> Zoom <input type='range' step='.05' min='0.1' max='5.45' value='1' oninput='${updateZoom}' style="margin-top:10px;" class='jrange controls' disabled='disabled'></label>
|
|
334
|
-
<label class="lm-f1 lm-p6"> Rotate <input type='range' step='.05' min='-1' max='1' value='0' oninput='${updateRotate}' style="margin-top:10px;" class='jrange controls' disabled='disabled'></label>
|
|
335
|
-
</div>
|
|
336
|
-
</div>
|
|
337
|
-
<div>
|
|
338
|
-
<div class="lm-center lm-row">
|
|
339
|
-
<label class="lm-f1 lm-p6"> Brigthness <input type='range' min='-1' max='1' step='.05' value='0' :bind='self.brightness' oninput='${setBrightness}' style="margin-top:10px;" class='jrange controls' disabled='disabled'></label>
|
|
340
|
-
<label class="lm-f1 lm-p6"> Contrast <input type='range' min='-1' max='1' step='.05' value='0' :bind='self.contrast' oninput='${setContrast}' style="margin-top:10px;" class='jrange controls' disabled='disabled'></label>
|
|
341
|
-
</div>
|
|
342
|
-
</div>
|
|
343
|
-
</div>
|
|
299
|
+
<div class="controls">
|
|
300
|
+
<div style="display: flex; justify-content: center; gap: 10px; text-align: center; border-top: 1px solid #ddd;">
|
|
301
|
+
<label style="padding: 20px;">Zoom<br><input type='range' step='.05' min='0.1' max='5.45' value='1' oninput='${updateZoom}' class='jrange controls' disabled='disabled'></label>
|
|
302
|
+
<label style="padding: 20px;">Rotate<br><input type='range' step='.05' min='-1' max='1' value='0' oninput='${updateRotate}'class='jrange controls' disabled='disabled'></label>
|
|
303
|
+
<label style="padding: 20px;">Brigthness<br><input type='range' min='-1' max='1' step='.05' value='0' :bind='self.brightness' oninput='${setBrightness}' class='jrange controls' disabled='disabled'></label>
|
|
304
|
+
<label style="padding: 20px;">Contrast<br><input type='range' min='-1' max='1' step='.05' value='0' :bind='self.contrast' oninput='${setContrast}' class='jrange controls' disabled='disabled'></label>
|
|
344
305
|
</div>
|
|
345
|
-
<div class='lm-row
|
|
346
|
-
<div class='lm-column lm-
|
|
347
|
-
<input type='button' value='Save Photo' class='lm-button controls' style='
|
|
348
|
-
</div><div class='lm-column
|
|
349
|
-
<input type='button' value='Upload Photo' class='lm-button' style='
|
|
350
|
-
</div><div class='lm-column
|
|
351
|
-
<input type='button' value='Delete Photo' class='lm-button controls' style='
|
|
306
|
+
<div class='lm-row lm-p15' style='border-top: 1px solid #ddd'>
|
|
307
|
+
<div class='lm-column lm-f1'>
|
|
308
|
+
<input type='button' value='Save Photo' class='lm-button controls' style='width: 100%' onclick='${updatePhoto}' disabled='disabled'>
|
|
309
|
+
</div><div class='lm-column'>
|
|
310
|
+
<input type='button' value='Upload Photo' class='lm-button' style='width: 100%' onclick='${uploadPhoto}'>
|
|
311
|
+
</div><div class='lm-column' style='text-align:right'>
|
|
312
|
+
<input type='button' value='Delete Photo' class='lm-button controls' style='width: 100%' onclick='${deletePhoto}' disabled='disabled'>
|
|
352
313
|
</div>
|
|
353
314
|
</div>
|
|
354
315
|
</div>
|
|
355
|
-
</
|
|
356
|
-
<
|
|
316
|
+
</lm-modal>
|
|
317
|
+
<lm-contextmenu options="${menu}"></lm-contextmenu>
|
|
357
318
|
</div>`;
|
|
358
319
|
}
|
|
359
320
|
|
|
360
321
|
lemonade.setComponents({ Cropper: Cropper });
|
|
322
|
+
// Register the web component
|
|
323
|
+
lemonade.createWebComponent('cropper', Cropper);
|
|
361
324
|
|
|
362
325
|
return function (root, options, template) {
|
|
363
326
|
if (typeof (root) === 'object') {
|
package/package.json
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@lemonadejs/cropper",
|
|
3
|
-
"title": "JavaScript image cropper editor.",
|
|
4
|
-
"description": "LemonadeJS and jSuites cropper editor integration",
|
|
5
|
-
"author": {
|
|
6
|
-
"name": "Contact <contact@lemonadejs.net>",
|
|
7
|
-
"url": "https://lemonadejs.net"
|
|
8
|
-
},
|
|
9
|
-
"keywords": [
|
|
10
|
-
"javascript cropper",
|
|
11
|
-
"online image editor",
|
|
12
|
-
"js",
|
|
13
|
-
"library",
|
|
14
|
-
"javascript plugins"
|
|
15
|
-
],
|
|
16
|
-
"dependencies": {
|
|
17
|
-
"lemonadejs": "^5.
|
|
18
|
-
"@lemonadejs/studio": "^5.
|
|
19
|
-
"jsuites": "^
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@lemonadejs/cropper",
|
|
3
|
+
"title": "JavaScript image cropper editor.",
|
|
4
|
+
"description": "LemonadeJS and jSuites cropper editor integration",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Contact <contact@lemonadejs.net>",
|
|
7
|
+
"url": "https://lemonadejs.net"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"javascript cropper",
|
|
11
|
+
"online image editor",
|
|
12
|
+
"js",
|
|
13
|
+
"library",
|
|
14
|
+
"javascript plugins"
|
|
15
|
+
],
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"lemonadejs": "^5.3.3",
|
|
18
|
+
"@lemonadejs/studio": "^5.7.11",
|
|
19
|
+
"@jsuites/cropper": "^1.7.0"
|
|
20
|
+
},
|
|
21
|
+
"main": "dist/index.js",
|
|
22
|
+
"version": "6.0.0"
|
|
23
|
+
}
|