@lemonadejs/cropper 5.0.0 → 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 +317 -0
- package/package.json +23 -24
- package/dist/cropper.js +0 -370
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
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* (c) LemonadeJS components
|
|
3
|
+
*
|
|
4
|
+
* Website: https://lemonadejs.com
|
|
5
|
+
* Description: Image cropper v5
|
|
6
|
+
*
|
|
7
|
+
* MIT License
|
|
8
|
+
*
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
if (!lemonade && typeof (require) === 'function') {
|
|
12
|
+
var lemonade = require('lemonadejs');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!studio && typeof (require) === 'function') {
|
|
16
|
+
var studio = require('@lemonadejs/studio');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (typeof(cropper) === 'undefined') {
|
|
20
|
+
if (typeof(require) === 'function') {
|
|
21
|
+
var Crop = require('@jsuites/cropper');
|
|
22
|
+
} else if (window.cropper) {
|
|
23
|
+
var Crop = window.cropper;
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
var Crop = cropper;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
;(function (global, factory) {
|
|
30
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
31
|
+
typeof define === 'function' && define.amd ? define(factory) :
|
|
32
|
+
global.Cropper = factory();
|
|
33
|
+
}(this, (function () {
|
|
34
|
+
|
|
35
|
+
'use strict';
|
|
36
|
+
|
|
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;
|
|
59
|
+
let crop = null;
|
|
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
|
+
]
|
|
78
|
+
|
|
79
|
+
self.cropperArea = null;
|
|
80
|
+
self.brightness = 0;
|
|
81
|
+
self.contrast = 0;
|
|
82
|
+
self.greyscale = 0;
|
|
83
|
+
|
|
84
|
+
self.createCropper = (o) => {
|
|
85
|
+
const area = getWindowWidth();
|
|
86
|
+
let a;
|
|
87
|
+
let c;
|
|
88
|
+
if (area < 800) {
|
|
89
|
+
a = [ area, area ];
|
|
90
|
+
c = [ area, area ];
|
|
91
|
+
} else {
|
|
92
|
+
a = [798, 360];
|
|
93
|
+
c = [width, height];
|
|
94
|
+
}
|
|
95
|
+
crop = Crop(o, {
|
|
96
|
+
area: a,
|
|
97
|
+
crop: c,
|
|
98
|
+
allowResize: false,
|
|
99
|
+
onchange: (el, image) => {
|
|
100
|
+
if (image) {
|
|
101
|
+
self.setControls(true);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const updateZoom = (e) => {
|
|
108
|
+
crop.zoom(e.target.value);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const updateRotate = (e) => {
|
|
112
|
+
crop.rotate(e.target.value);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const setBrightness = (e) => {
|
|
116
|
+
crop.brightness(e.target.value);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const setContrast = (e) => {
|
|
120
|
+
crop.contrast(e.target.value);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const updatePhoto = () => {
|
|
124
|
+
// Checks if cropper container is editable
|
|
125
|
+
if (self.cropperArea.classList.contains('jcrop_edition')) {
|
|
126
|
+
self.image.innerHTML = '';
|
|
127
|
+
// Create image with metadata
|
|
128
|
+
const newImage = crop.getCroppedImage();
|
|
129
|
+
// Callback for the blob
|
|
130
|
+
const createImage = (b) => {
|
|
131
|
+
// Transform to blob
|
|
132
|
+
const filename = window.URL.createObjectURL(b);
|
|
133
|
+
// Set upload information
|
|
134
|
+
const data = {
|
|
135
|
+
file: filename,
|
|
136
|
+
content: newImage.src,
|
|
137
|
+
extension: newImage.content.extension,
|
|
138
|
+
};
|
|
139
|
+
// Upload original image
|
|
140
|
+
if (original === true) {
|
|
141
|
+
data.original = crop.getImage().src;
|
|
142
|
+
}
|
|
143
|
+
// Update file to blob
|
|
144
|
+
newImage.src = filename;
|
|
145
|
+
// Integration with
|
|
146
|
+
if (self.name) {
|
|
147
|
+
newImage.classList.remove('jfile');
|
|
148
|
+
}
|
|
149
|
+
// Value
|
|
150
|
+
self.value = [data];
|
|
151
|
+
// Append new image
|
|
152
|
+
self.image.appendChild(newImage);
|
|
153
|
+
}
|
|
154
|
+
// Create image
|
|
155
|
+
crop.getCroppedAsBlob(createImage);
|
|
156
|
+
// Close the modal
|
|
157
|
+
setTimeout(() => {
|
|
158
|
+
self.modal.close();
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const uploadPhoto = () => {
|
|
164
|
+
crop.getFileInput().click();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const deletePhoto = () => {
|
|
168
|
+
if (self.image) {
|
|
169
|
+
// Reset photo from crop
|
|
170
|
+
crop.reset();
|
|
171
|
+
// Disable controls
|
|
172
|
+
self.setControls(false);
|
|
173
|
+
// Reset from container
|
|
174
|
+
self.image.innerHTML = '';
|
|
175
|
+
// Reset container
|
|
176
|
+
self.value = '';
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
self.setControls = (state) => {
|
|
181
|
+
const controls = self.el.querySelectorAll('input.controls');
|
|
182
|
+
if (state === false) {
|
|
183
|
+
for (let i = 0; i < controls.length; i++) {
|
|
184
|
+
controls[i].setAttribute('disabled', 'disabled');
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
for (let i = 0; i < controls.length; i++) {
|
|
188
|
+
controls[i].removeAttribute('disabled');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
for (let i = 0; i < controls.length; i++) {
|
|
193
|
+
if (controls[i].type === 'range') {
|
|
194
|
+
controls[i].value = 0;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
self.getValue = () => {
|
|
200
|
+
return self.value;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
self.setValue = (data) => {
|
|
204
|
+
if (!data) {
|
|
205
|
+
// Reset container
|
|
206
|
+
deletePhoto();
|
|
207
|
+
} else {
|
|
208
|
+
if (typeof(data) === 'string') {
|
|
209
|
+
data = {
|
|
210
|
+
file: data
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (data.file) {
|
|
215
|
+
const img = document.createElement('img');
|
|
216
|
+
img.setAttribute('src', data.file);
|
|
217
|
+
img.setAttribute('tabindex', -1);
|
|
218
|
+
self.image.innerHTML = '';
|
|
219
|
+
self.image.appendChild(img);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (data.original) {
|
|
223
|
+
crop.addFromFile(data.original);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
self.value = [data];
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
self.open = () => {
|
|
231
|
+
if (self.modal.isClosed()) {
|
|
232
|
+
self.modal.open();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
onload(() => {
|
|
237
|
+
self.image.style.maxWidth = self.width + 'px';
|
|
238
|
+
self.image.style.maxHeight = self.height + 'px';
|
|
239
|
+
|
|
240
|
+
// Onclick event
|
|
241
|
+
self.image.addEventListener('mousedown', (e) => {
|
|
242
|
+
let mouseButton;
|
|
243
|
+
const event = e || window.event;
|
|
244
|
+
if (event.buttons) {
|
|
245
|
+
mouseButton = event.buttons;
|
|
246
|
+
} else if (event.button) {
|
|
247
|
+
mouseButton = event.button;
|
|
248
|
+
} else {
|
|
249
|
+
mouseButton = event.which;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (mouseButton === 1) {
|
|
253
|
+
self.open();
|
|
254
|
+
} else {
|
|
255
|
+
if (event.target.tagName === 'IMG') {
|
|
256
|
+
event.target.focus();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
self.el.addEventListener('onkeydown', (e) => {
|
|
262
|
+
if (e.key === 'Delete' && e.target.tagName === 'IMG') {
|
|
263
|
+
deletePhoto();
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
self.el.val = (v) => {
|
|
268
|
+
if (v === undefined) {
|
|
269
|
+
return self.getValue();
|
|
270
|
+
} else {
|
|
271
|
+
self.setValue(v);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
track('value');
|
|
277
|
+
|
|
278
|
+
// Template
|
|
279
|
+
return render => render `<div name="{{self.name}}">
|
|
280
|
+
<div :ref='this.image' class="jphoto jcropper"></div>
|
|
281
|
+
<lm-modal draggable="true" closable="true" closed="true" width="800" height="570" title="Photo Upload" icon="photo" :ref="self.modal">
|
|
282
|
+
<div :ready='self.createCropper' :ref='self.cropperArea'></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>
|
|
289
|
+
</div>
|
|
290
|
+
<div class='lm-row lm-p20' style='border-top: 1px solid #ddd'>
|
|
291
|
+
<div class='lm-column lm-p6 lm-f1'>
|
|
292
|
+
<input type='button' value='Save Photo' class='lm-button controls' style='min-width: 140px;' onclick='${updatePhoto}' disabled='disabled'>
|
|
293
|
+
</div><div class='lm-column lm-p6'>
|
|
294
|
+
<input type='button' value='Upload Photo' class='lm-button' style='min-width: 140px;' onclick='${uploadPhoto}'>
|
|
295
|
+
</div><div class='lm-column lm-p6' style='text-align:right'>
|
|
296
|
+
<input type='button' value='Delete Photo' class='lm-button controls' style='min-width: 140px;' onclick='${deletePhoto}' disabled='disabled'>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
</lm-modal>
|
|
301
|
+
<lm-contextmenu options="${menu}"></lm-contextmenu>
|
|
302
|
+
</div>`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
lemonade.setComponents({ Cropper: Cropper });
|
|
306
|
+
// Register the web component
|
|
307
|
+
lemonade.createWebComponent('cropper', Cropper);
|
|
308
|
+
|
|
309
|
+
return function (root, options, template) {
|
|
310
|
+
if (typeof (root) === 'object') {
|
|
311
|
+
lemonade.render(Cropper, root, options, template)
|
|
312
|
+
return options;
|
|
313
|
+
} else {
|
|
314
|
+
return Cropper.call(this, root, template)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
})));
|
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
|
+
}
|
package/dist/cropper.js
DELETED
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* (c) LemonadeJS components
|
|
3
|
-
*
|
|
4
|
-
* Website: https://lemonadejs.com
|
|
5
|
-
* Description: Image cropper v5
|
|
6
|
-
*
|
|
7
|
-
* MIT License
|
|
8
|
-
*
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
if (!lemonade && typeof (require) === 'function') {
|
|
12
|
-
var lemonade = require('lemonadejs');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (!studio && typeof (require) === 'function') {
|
|
16
|
-
var studio = require('@lemonadejs/studio');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Load jsuites
|
|
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') {
|
|
29
|
-
if (typeof(require) === 'function') {
|
|
30
|
-
var Crop = require('@jsuites/cropper');
|
|
31
|
-
} else if (window.cropper) {
|
|
32
|
-
var Crop = window.cropper;
|
|
33
|
-
}
|
|
34
|
-
} else {
|
|
35
|
-
var Crop = cropper;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
;(function (global, factory) {
|
|
39
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
40
|
-
typeof define === 'function' && define.amd ? define(factory) :
|
|
41
|
-
global.Cropper = factory();
|
|
42
|
-
}(this, (function () {
|
|
43
|
-
|
|
44
|
-
'use strict';
|
|
45
|
-
|
|
46
|
-
const Cropper = function() {
|
|
47
|
-
let self = this;
|
|
48
|
-
let original = self.original ? 1 : 0;
|
|
49
|
-
let width = self.width || 300;
|
|
50
|
-
let height = self.height || 240;
|
|
51
|
-
let modal = null;
|
|
52
|
-
let crop = null;
|
|
53
|
-
let menu = null;
|
|
54
|
-
|
|
55
|
-
self.cropperArea = null;
|
|
56
|
-
self.brightness = 0;
|
|
57
|
-
self.contrast = 0;
|
|
58
|
-
self.greyscale = 0;
|
|
59
|
-
|
|
60
|
-
// Methods
|
|
61
|
-
self.createModal = function(o) {
|
|
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();
|
|
74
|
-
let a;
|
|
75
|
-
let c;
|
|
76
|
-
if (area < 800) {
|
|
77
|
-
a = [ area, area ];
|
|
78
|
-
c = [ area, area ];
|
|
79
|
-
} else {
|
|
80
|
-
a = [798, 360];
|
|
81
|
-
c = [width, height];
|
|
82
|
-
}
|
|
83
|
-
crop = Crop(o, {
|
|
84
|
-
area: a,
|
|
85
|
-
crop: c ,
|
|
86
|
-
allowResize: false,
|
|
87
|
-
onchange: function(el, image) {
|
|
88
|
-
if (image) {
|
|
89
|
-
self.setControls(true);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
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
|
-
const updateZoom = (e) => {
|
|
127
|
-
crop.zoom(e.target.value);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const updateRotate = (e) => {
|
|
131
|
-
crop.rotate(e.target.value);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const setBrightness = (e) => {
|
|
135
|
-
crop.brightness(e.target.value);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const setContrast = (e) => {
|
|
139
|
-
crop.contrast(e.target.value);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const updatePhoto = () => {
|
|
143
|
-
// Checks if cropper container is editable
|
|
144
|
-
if (self.cropperArea.classList.contains('jcrop_edition')) {
|
|
145
|
-
self.image.innerHTML = '';
|
|
146
|
-
// Create image with metadata
|
|
147
|
-
let newImage = crop.getCroppedImage();
|
|
148
|
-
// Callback for the blob
|
|
149
|
-
let createImage = function(b) {
|
|
150
|
-
// Transform to blob
|
|
151
|
-
let filename = window.URL.createObjectURL(b);
|
|
152
|
-
// Set upload information
|
|
153
|
-
let data = {
|
|
154
|
-
file: filename,
|
|
155
|
-
content: newImage.src,
|
|
156
|
-
extension: newImage.content.extension,
|
|
157
|
-
};
|
|
158
|
-
// Upload original image
|
|
159
|
-
if (original == true) {
|
|
160
|
-
data.original = crop.getImage().src;
|
|
161
|
-
}
|
|
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
|
-
// Value
|
|
169
|
-
self.value = [data];
|
|
170
|
-
// Append new image
|
|
171
|
-
self.image.appendChild(newImage);
|
|
172
|
-
}
|
|
173
|
-
// Create image
|
|
174
|
-
crop.getCroppedAsBlob(createImage);
|
|
175
|
-
// Close the modal
|
|
176
|
-
setTimeout(function() {
|
|
177
|
-
modal.close();
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const uploadPhoto = () => {
|
|
183
|
-
jSuites.click(crop.getFileInput());
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const deletePhoto = function() {
|
|
187
|
-
if (self.image) {
|
|
188
|
-
// Reset photo from crop
|
|
189
|
-
crop.reset();
|
|
190
|
-
// Disable controls
|
|
191
|
-
self.setControls(false);
|
|
192
|
-
// Reset from container
|
|
193
|
-
self.image.innerHTML = '';
|
|
194
|
-
// Reset container
|
|
195
|
-
self.value = null;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
self.setControls = function(state) {
|
|
200
|
-
let controls = self.el.querySelectorAll('input.controls');
|
|
201
|
-
if (state == false) {
|
|
202
|
-
for (let i = 0; i < controls.length; i++) {
|
|
203
|
-
controls[i].setAttribute('disabled', 'disabled');
|
|
204
|
-
}
|
|
205
|
-
} else {
|
|
206
|
-
for (let i = 0; i < controls.length; i++) {
|
|
207
|
-
controls[i].removeAttribute('disabled');
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
for (let i = 0; i < controls.length; i++) {
|
|
212
|
-
if (controls[i].type === 'range') {
|
|
213
|
-
controls[i].value = 0;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
self.getValue = function() {
|
|
219
|
-
return self.value;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
self.setValue = function(data) {
|
|
223
|
-
if (! data) {
|
|
224
|
-
// Reset container
|
|
225
|
-
deletePhoto();
|
|
226
|
-
} else {
|
|
227
|
-
if (typeof(data) == 'string') {
|
|
228
|
-
data = {
|
|
229
|
-
file: data
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
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
|
-
if (data.original) {
|
|
242
|
-
crop.addFromFile(data.original);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
self.value = [data];
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
self.open = function() {
|
|
250
|
-
if (! modal.isOpen()) {
|
|
251
|
-
modal.open();
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
self.onload = function() {
|
|
256
|
-
self.image.style.maxWidth = self.width + 'px';
|
|
257
|
-
self.image.style.maxHeight = self.height + 'px';
|
|
258
|
-
|
|
259
|
-
// Onclick event
|
|
260
|
-
self.el.addEventListener('mousedown', function(e) {
|
|
261
|
-
let mouseButton;
|
|
262
|
-
e = e || window.event;
|
|
263
|
-
if (e.buttons) {
|
|
264
|
-
mouseButton = e.buttons;
|
|
265
|
-
} else if (e.button) {
|
|
266
|
-
mouseButton = e.button;
|
|
267
|
-
} else {
|
|
268
|
-
mouseButton = e.which;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (mouseButton == 1) {
|
|
272
|
-
self.open();
|
|
273
|
-
} else {
|
|
274
|
-
if (e.target.tagName == 'IMG') {
|
|
275
|
-
e.target.focus();
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
self.el.addEventListener('contextmenu', function(e) {
|
|
281
|
-
if (e.target.tagName == 'IMG') {
|
|
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') {
|
|
305
|
-
deletePhoto();
|
|
306
|
-
}
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
self.el.val = function(v) {
|
|
310
|
-
if (v === undefined) {
|
|
311
|
-
return self.getValue();
|
|
312
|
-
} else {
|
|
313
|
-
self.setValue(v);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Template
|
|
320
|
-
return render => render `<div name="{{self.name}}" value="{{self.value}}">
|
|
321
|
-
<div :ref='this.image' class="jphoto jcropper"></div>
|
|
322
|
-
<div :ready='self.createModal'>
|
|
323
|
-
<div :ready='self.createCropper' :ref='self.cropperArea'></div>
|
|
324
|
-
<div :ready='self.createControls' class="controls">
|
|
325
|
-
<div role='tabs'>
|
|
326
|
-
<div role='headers'>
|
|
327
|
-
<div style="background-color: white; padding: 15px !important;"></div>
|
|
328
|
-
<div style="background-color: white;"></div>
|
|
329
|
-
</div>
|
|
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>
|
|
344
|
-
</div>
|
|
345
|
-
<div class='lm-row om-p20' style='border-top: 1px solid #aaa'>
|
|
346
|
-
<div class='lm-column lm-p6 lm-f1'>
|
|
347
|
-
<input type='button' value='Save Photo' class='lm-button controls' style='min-width: 140px;' onclick='${updatePhoto}' disabled='disabled'>
|
|
348
|
-
</div><div class='lm-column lm-p6'>
|
|
349
|
-
<input type='button' value='Upload Photo' class='lm-button' style='min-width: 140px;' onclick='${uploadPhoto}'>
|
|
350
|
-
</div><div class='lm-column lm-p6' style='text-align:right'>
|
|
351
|
-
<input type='button' value='Delete Photo' class='lm-button controls' style='min-width: 140px;' onclick='${deletePhoto}' disabled='disabled'>
|
|
352
|
-
</div>
|
|
353
|
-
</div>
|
|
354
|
-
</div>
|
|
355
|
-
</div>
|
|
356
|
-
<div :ready="self.createMenu"></div>
|
|
357
|
-
</div>`;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
lemonade.setComponents({ Cropper: Cropper });
|
|
361
|
-
|
|
362
|
-
return function (root, options, template) {
|
|
363
|
-
if (typeof (root) === 'object') {
|
|
364
|
-
lemonade.render(Cropper, root, options, template)
|
|
365
|
-
return options;
|
|
366
|
-
} else {
|
|
367
|
-
return Cropper.call(this, root, template)
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
})));
|