@lemonadejs/cropper 5.0.1 → 5.1.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 +334 -1
- package/dist/index.d.ts +119 -0
- package/dist/index.js +97 -150
- package/package.json +23 -24
package/README.md
CHANGED
|
@@ -1 +1,334 @@
|
|
|
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[]` - Array 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 | 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
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Related Projects
|
|
314
|
+
|
|
315
|
+
- **[LemonadeJS](https://lemonadejs.com)** - Reactive micro JavaScript library
|
|
316
|
+
- **[jSuites](https://jsuites.net)** - JavaScript plugins and web components collection
|
|
317
|
+
- **[jSpreadsheet](https://jspreadsheet.com)** - JavaScript data grid and spreadsheet component
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
## License
|
|
321
|
+
|
|
322
|
+
MIT License
|
|
323
|
+
|
|
324
|
+
## Support & Community
|
|
325
|
+
|
|
326
|
+
- **Documentation**: [https://lemonadejs.com/plugins/cropper](https://lemonadejs.com/plugins/cropper)
|
|
327
|
+
- **GitHub Issues**: Report bugs and request features
|
|
328
|
+
- **Community**: Join our community for support and discussions
|
|
329
|
+
- **Website**: [https://lemonadejs.com](https://lemonadejs.com)
|
|
330
|
+
|
|
331
|
+
## Contributing
|
|
332
|
+
|
|
333
|
+
Contributions are welcome! Please feel free to submit pull requests or open issues.
|
|
334
|
+
|
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
|
}
|
|
@@ -144,24 +125,24 @@ if (typeof(cropper) == 'undefined') {
|
|
|
144
125
|
if (self.cropperArea.classList.contains('jcrop_edition')) {
|
|
145
126
|
self.image.innerHTML = '';
|
|
146
127
|
// Create image with metadata
|
|
147
|
-
|
|
128
|
+
const newImage = crop.getCroppedImage();
|
|
148
129
|
// Callback for the blob
|
|
149
|
-
|
|
130
|
+
const createImage = (b) => {
|
|
150
131
|
// Transform to blob
|
|
151
|
-
|
|
132
|
+
const filename = window.URL.createObjectURL(b);
|
|
152
133
|
// Set upload information
|
|
153
|
-
|
|
134
|
+
const data = {
|
|
154
135
|
file: filename,
|
|
155
136
|
content: newImage.src,
|
|
156
137
|
extension: newImage.content.extension,
|
|
157
138
|
};
|
|
158
139
|
// Upload original image
|
|
159
|
-
if (original
|
|
140
|
+
if (original === true) {
|
|
160
141
|
data.original = crop.getImage().src;
|
|
161
142
|
}
|
|
162
143
|
// Update file to blob
|
|
163
144
|
newImage.src = filename;
|
|
164
|
-
// Integration with
|
|
145
|
+
// Integration with
|
|
165
146
|
if (self.name) {
|
|
166
147
|
newImage.classList.remove('jfile');
|
|
167
148
|
}
|
|
@@ -173,17 +154,17 @@ if (typeof(cropper) == 'undefined') {
|
|
|
173
154
|
// Create image
|
|
174
155
|
crop.getCroppedAsBlob(createImage);
|
|
175
156
|
// Close the modal
|
|
176
|
-
setTimeout(
|
|
177
|
-
modal.close();
|
|
157
|
+
setTimeout(() => {
|
|
158
|
+
self.modal.close();
|
|
178
159
|
});
|
|
179
160
|
}
|
|
180
161
|
}
|
|
181
162
|
|
|
182
163
|
const uploadPhoto = () => {
|
|
183
|
-
|
|
164
|
+
crop.getFileInput().click();
|
|
184
165
|
}
|
|
185
166
|
|
|
186
|
-
const deletePhoto =
|
|
167
|
+
const deletePhoto = () => {
|
|
187
168
|
if (self.image) {
|
|
188
169
|
// Reset photo from crop
|
|
189
170
|
crop.reset();
|
|
@@ -192,13 +173,13 @@ if (typeof(cropper) == 'undefined') {
|
|
|
192
173
|
// Reset from container
|
|
193
174
|
self.image.innerHTML = '';
|
|
194
175
|
// Reset container
|
|
195
|
-
self.value =
|
|
176
|
+
self.value = '';
|
|
196
177
|
}
|
|
197
178
|
}
|
|
198
179
|
|
|
199
|
-
self.setControls =
|
|
200
|
-
|
|
201
|
-
if (state
|
|
180
|
+
self.setControls = (state) => {
|
|
181
|
+
const controls = self.el.querySelectorAll('input.controls');
|
|
182
|
+
if (state === false) {
|
|
202
183
|
for (let i = 0; i < controls.length; i++) {
|
|
203
184
|
controls[i].setAttribute('disabled', 'disabled');
|
|
204
185
|
}
|
|
@@ -215,23 +196,23 @@ if (typeof(cropper) == 'undefined') {
|
|
|
215
196
|
}
|
|
216
197
|
}
|
|
217
198
|
|
|
218
|
-
self.getValue =
|
|
199
|
+
self.getValue = () => {
|
|
219
200
|
return self.value;
|
|
220
201
|
}
|
|
221
202
|
|
|
222
|
-
self.setValue =
|
|
223
|
-
if (!
|
|
203
|
+
self.setValue = (data) => {
|
|
204
|
+
if (!data) {
|
|
224
205
|
// Reset container
|
|
225
206
|
deletePhoto();
|
|
226
207
|
} else {
|
|
227
|
-
if (typeof(data)
|
|
208
|
+
if (typeof(data) === 'string') {
|
|
228
209
|
data = {
|
|
229
210
|
file: data
|
|
230
211
|
}
|
|
231
212
|
}
|
|
232
213
|
|
|
233
214
|
if (data.file) {
|
|
234
|
-
|
|
215
|
+
const img = document.createElement('img');
|
|
235
216
|
img.setAttribute('src', data.file);
|
|
236
217
|
img.setAttribute('tabindex', -1);
|
|
237
218
|
self.image.innerHTML = '';
|
|
@@ -246,103 +227,67 @@ if (typeof(cropper) == 'undefined') {
|
|
|
246
227
|
}
|
|
247
228
|
}
|
|
248
229
|
|
|
249
|
-
self.open =
|
|
250
|
-
if (
|
|
251
|
-
modal.open();
|
|
230
|
+
self.open = () => {
|
|
231
|
+
if (self.modal.isClosed()) {
|
|
232
|
+
self.modal.open();
|
|
252
233
|
}
|
|
253
234
|
}
|
|
254
235
|
|
|
255
|
-
|
|
236
|
+
onload(() => {
|
|
256
237
|
self.image.style.maxWidth = self.width + 'px';
|
|
257
238
|
self.image.style.maxHeight = self.height + 'px';
|
|
258
239
|
|
|
259
240
|
// Onclick event
|
|
260
|
-
self.
|
|
241
|
+
self.image.addEventListener('mousedown', (e) => {
|
|
261
242
|
let mouseButton;
|
|
262
|
-
|
|
263
|
-
if (
|
|
264
|
-
mouseButton =
|
|
265
|
-
} else if (
|
|
266
|
-
mouseButton =
|
|
243
|
+
const event = e || window.event;
|
|
244
|
+
if (event.buttons) {
|
|
245
|
+
mouseButton = event.buttons;
|
|
246
|
+
} else if (event.button) {
|
|
247
|
+
mouseButton = event.button;
|
|
267
248
|
} else {
|
|
268
|
-
mouseButton =
|
|
249
|
+
mouseButton = event.which;
|
|
269
250
|
}
|
|
270
251
|
|
|
271
|
-
if (mouseButton
|
|
252
|
+
if (mouseButton === 1) {
|
|
272
253
|
self.open();
|
|
273
254
|
} else {
|
|
274
|
-
if (
|
|
275
|
-
|
|
255
|
+
if (event.target.tagName === 'IMG') {
|
|
256
|
+
event.target.focus();
|
|
276
257
|
}
|
|
277
258
|
}
|
|
278
259
|
});
|
|
279
260
|
|
|
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') {
|
|
261
|
+
self.el.addEventListener('onkeydown', (e) => {
|
|
262
|
+
if (e.key === 'Delete' && e.target.tagName === 'IMG') {
|
|
305
263
|
deletePhoto();
|
|
306
264
|
}
|
|
307
|
-
})
|
|
265
|
+
});
|
|
308
266
|
|
|
309
|
-
self.el.val =
|
|
267
|
+
self.el.val = (v) => {
|
|
310
268
|
if (v === undefined) {
|
|
311
269
|
return self.getValue();
|
|
312
270
|
} else {
|
|
313
271
|
self.setValue(v);
|
|
314
272
|
}
|
|
315
273
|
}
|
|
274
|
+
});
|
|
316
275
|
|
|
317
|
-
|
|
276
|
+
track('value');
|
|
318
277
|
|
|
319
278
|
// Template
|
|
320
|
-
return render => render `<div name="{{self.name}}"
|
|
279
|
+
return render => render `<div name="{{self.name}}">
|
|
321
280
|
<div :ref='this.image' class="jphoto jcropper"></div>
|
|
322
|
-
<
|
|
281
|
+
<lm-modal draggable="true" closable="true" closed="true" width="800" height="570" title="Photo Upload" icon="photo" :ref="self.modal">
|
|
323
282
|
<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>
|
|
283
|
+
<div class="controls">
|
|
284
|
+
<div style="display: flex; justify-content: center; gap: 10px; text-align: center; border-top: 1px solid #ddd;">
|
|
285
|
+
<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>
|
|
286
|
+
<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>
|
|
287
|
+
<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>
|
|
288
|
+
<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
289
|
</div>
|
|
345
|
-
<div class='lm-row
|
|
290
|
+
<div class='lm-row lm-p20' style='border-top: 1px solid #ddd'>
|
|
346
291
|
<div class='lm-column lm-p6 lm-f1'>
|
|
347
292
|
<input type='button' value='Save Photo' class='lm-button controls' style='min-width: 140px;' onclick='${updatePhoto}' disabled='disabled'>
|
|
348
293
|
</div><div class='lm-column lm-p6'>
|
|
@@ -352,12 +297,14 @@ if (typeof(cropper) == 'undefined') {
|
|
|
352
297
|
</div>
|
|
353
298
|
</div>
|
|
354
299
|
</div>
|
|
355
|
-
</
|
|
356
|
-
<
|
|
300
|
+
</lm-modal>
|
|
301
|
+
<lm-contextmenu options="${menu}"></lm-contextmenu>
|
|
357
302
|
</div>`;
|
|
358
303
|
}
|
|
359
304
|
|
|
360
305
|
lemonade.setComponents({ Cropper: Cropper });
|
|
306
|
+
// Register the web component
|
|
307
|
+
lemonade.createWebComponent('cropper', Cropper);
|
|
361
308
|
|
|
362
309
|
return function (root, options, template) {
|
|
363
310
|
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.2",
|
|
18
|
+
"@lemonadejs/studio": "^5.7.0",
|
|
19
|
+
"@jsuites/cropper": "^1.7.0"
|
|
20
|
+
},
|
|
21
|
+
"main": "dist/index.js",
|
|
22
|
+
"version": "5.1.0"
|
|
23
|
+
}
|