@plone-collective/volto-image-editor 1.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/.changelog.draft +19 -0
  2. package/.release-it.json +34 -0
  3. package/CHANGELOG.md +27 -0
  4. package/README.md +209 -0
  5. package/babel.config.js +17 -0
  6. package/locales/de/LC_MESSAGES/volto.po +187 -0
  7. package/locales/en/LC_MESSAGES/volto.po +187 -0
  8. package/locales/es/LC_MESSAGES/volto.po +194 -0
  9. package/locales/pt_BR/LC_MESSAGES/volto.po +192 -0
  10. package/locales/volto.pot +189 -0
  11. package/news/.gitkeep +0 -0
  12. package/package.json +50 -0
  13. package/public/.gitkeep +0 -0
  14. package/src/components/.gitkeep +0 -0
  15. package/src/components/ImageEditor/ImageEditor.scss +59 -0
  16. package/src/components/ImageEditor/ImageEditor.tsx +290 -0
  17. package/src/components/ImageEditor/ImageEditorWrapper.scss +13 -0
  18. package/src/components/ImageEditor/ImageEditorWrapper.tsx +91 -0
  19. package/src/components/ImageEditor/components/AdjustableCropperBackground.tsx +60 -0
  20. package/src/components/ImageEditor/components/AdjustableImage.scss +3 -0
  21. package/src/components/ImageEditor/components/AdjustableImage.tsx +85 -0
  22. package/src/components/ImageEditor/components/AdjustablePreviewBackground.tsx +59 -0
  23. package/src/components/ImageEditor/components/Button.scss +30 -0
  24. package/src/components/ImageEditor/components/Button.tsx +27 -0
  25. package/src/components/ImageEditor/components/Navigation.scss +38 -0
  26. package/src/components/ImageEditor/components/Navigation.tsx +153 -0
  27. package/src/components/ImageEditor/components/SettingsModal.scss +369 -0
  28. package/src/components/ImageEditor/components/SettingsModal.tsx +582 -0
  29. package/src/components/ImageEditor/components/Slider.scss +231 -0
  30. package/src/components/ImageEditor/components/Slider.tsx +322 -0
  31. package/src/components/ImageEditor/types/ImageSettings.ts +23 -0
  32. package/src/components/Widgets/FileWidget.scss +14 -0
  33. package/src/components/Widgets/FileWidget.tsx +239 -0
  34. package/src/config/settings.ts +5 -0
  35. package/src/customizations/volto/components/manage/Widgets/FileWidget.jsx +3 -0
  36. package/src/icons/AspectRatioIcons.tsx +39 -0
  37. package/src/icons/BrightnessIcon.tsx +19 -0
  38. package/src/icons/ContrastIcon.tsx +19 -0
  39. package/src/icons/CropIcon.tsx +19 -0
  40. package/src/icons/DownloadIcon.tsx +20 -0
  41. package/src/icons/FlipHorizontalIcon.tsx +21 -0
  42. package/src/icons/FlipVerticalIcon.tsx +27 -0
  43. package/src/icons/HueIcon.tsx +20 -0
  44. package/src/icons/ResetIcon.tsx +19 -0
  45. package/src/icons/ResetRotationIcon.tsx +20 -0
  46. package/src/icons/RestrictionIcons.tsx +19 -0
  47. package/src/icons/RotateLeftIcon.tsx +21 -0
  48. package/src/icons/RotateRightIcon.tsx +21 -0
  49. package/src/icons/SaturationIcon.tsx +20 -0
  50. package/src/icons/SettingsIcon.tsx +21 -0
  51. package/src/icons/StencilIcons.tsx +8 -0
  52. package/src/index.ts +10 -0
  53. package/src/theme/constants.scss +13 -0
  54. package/src/theme/custom.css +82 -0
  55. package/src/theme/mixins.scss +91 -0
  56. package/towncrier.toml +33 -0
  57. package/tsconfig.json +30 -0
@@ -0,0 +1,153 @@
1
+ import React, { FC } from 'react';
2
+ import cn from 'classnames';
3
+ import { defineMessages, useIntl } from 'react-intl';
4
+ import Icon from '@plone/volto/components/theme/Icon/Icon';
5
+ import CancelSVG from '@plone/volto/icons/clear.svg';
6
+ import SaveSVG from '@plone/volto/icons/save.svg';
7
+ import { CropIcon } from '@plone-collective/volto-image-editor/icons/CropIcon';
8
+ import { HueIcon } from '@plone-collective/volto-image-editor/icons/HueIcon';
9
+ import { SaturationIcon } from '@plone-collective/volto-image-editor/icons/SaturationIcon';
10
+ import { ContrastIcon } from '@plone-collective/volto-image-editor/icons/ContrastIcon';
11
+ import { BrightnessIcon } from '@plone-collective/volto-image-editor/icons/BrightnessIcon';
12
+ import { RotateLeftIcon } from '@plone-collective/volto-image-editor/icons/RotateLeftIcon';
13
+ import { RotateRightIcon } from '@plone-collective/volto-image-editor/icons/RotateRightIcon';
14
+ import { FlipHorizontalIcon } from '@plone-collective/volto-image-editor/icons/FlipHorizontalIcon';
15
+ import { FlipVerticalIcon } from '@plone-collective/volto-image-editor/icons/FlipVerticalIcon';
16
+ import { ResetRotationIcon } from '@plone-collective/volto-image-editor/icons/ResetRotationIcon';
17
+ import { Button } from './Button';
18
+ import './Navigation.scss';
19
+
20
+ const messages = defineMessages({
21
+ resetRotationAndFlip: {
22
+ id: 'Reset rotation and flip',
23
+ defaultMessage: 'Reset rotation and flip',
24
+ },
25
+ });
26
+
27
+ interface Props {
28
+ className?: string;
29
+ mode?: string;
30
+ onChange?: (mode: string) => void;
31
+ onSave?: () => void;
32
+ onCancel?: () => void;
33
+ onAction?: (action: string) => void;
34
+ onResetRotation?: () => void;
35
+ hasRotationChanges?: boolean;
36
+ onUpload?: (blob: string) => void;
37
+ }
38
+
39
+ const CancelIcon = () => {
40
+ return <Icon name={CancelSVG} />;
41
+ };
42
+
43
+ const SaveIcon = () => {
44
+ return <Icon name={SaveSVG} />;
45
+ };
46
+
47
+ export const Navigation: FC<Props> = ({
48
+ className,
49
+ onChange,
50
+ onSave,
51
+ onCancel,
52
+ onAction,
53
+ onResetRotation,
54
+ hasRotationChanges,
55
+ mode,
56
+ }) => {
57
+ const intl = useIntl();
58
+
59
+ const setMode = (mode: string) => () => {
60
+ onChange?.(mode);
61
+ };
62
+
63
+ const handleAction = (action: string) => () => {
64
+ onAction?.(action);
65
+ };
66
+
67
+ return (
68
+ <div className={cn('image-editor-navigation', className)}>
69
+ <div className="image-editor-navigation__buttons">
70
+ <Button
71
+ className={'image-editor-navigation__button'}
72
+ active={mode === 'crop'}
73
+ onClick={setMode('crop')}
74
+ >
75
+ <CropIcon />
76
+ </Button>
77
+ <Button
78
+ className={'image-editor-navigation__button'}
79
+ onClick={handleAction('rotate-left')}
80
+ >
81
+ <RotateLeftIcon />
82
+ </Button>
83
+ <Button
84
+ className={'image-editor-navigation__button'}
85
+ onClick={handleAction('rotate-right')}
86
+ >
87
+ <RotateRightIcon />
88
+ </Button>
89
+ <Button
90
+ className={'image-editor-navigation__button'}
91
+ onClick={handleAction('flip-horizontal')}
92
+ >
93
+ <FlipHorizontalIcon />
94
+ </Button>
95
+ <Button
96
+ className={'image-editor-navigation__button'}
97
+ onClick={handleAction('flip-vertical')}
98
+ >
99
+ <FlipVerticalIcon />
100
+ </Button>
101
+ <Button
102
+ className={cn(
103
+ 'image-editor-navigation__button',
104
+ 'image-editor-navigation__reset-rotation',
105
+ !hasRotationChanges && 'image-editor-navigation__button--disabled',
106
+ )}
107
+ onClick={onResetRotation}
108
+ disabled={!hasRotationChanges}
109
+ title={intl.formatMessage(messages.resetRotationAndFlip)}
110
+ >
111
+ <ResetRotationIcon />
112
+ </Button>
113
+ <Button
114
+ className={'image-editor-navigation__button'}
115
+ active={mode === 'saturation'}
116
+ onClick={setMode('saturation')}
117
+ >
118
+ <SaturationIcon />
119
+ </Button>
120
+ <Button
121
+ className={'image-editor-navigation__button'}
122
+ active={mode === 'brightness'}
123
+ onClick={setMode('brightness')}
124
+ >
125
+ <BrightnessIcon />
126
+ </Button>
127
+ <Button
128
+ className={'image-editor-navigation__button'}
129
+ active={mode === 'contrast'}
130
+ onClick={setMode('contrast')}
131
+ >
132
+ <ContrastIcon />
133
+ </Button>
134
+ <Button
135
+ className={'image-editor-navigation__button'}
136
+ active={mode === 'hue'}
137
+ onClick={setMode('hue')}
138
+ >
139
+ <HueIcon />
140
+ </Button>
141
+ </div>
142
+ <Button
143
+ className={'image-editor-navigation__button cancel'}
144
+ onClick={onCancel}
145
+ >
146
+ <CancelIcon />
147
+ </Button>
148
+ <Button className={'image-editor-navigation__button'} onClick={onSave}>
149
+ <SaveIcon />
150
+ </Button>
151
+ </div>
152
+ );
153
+ };
@@ -0,0 +1,369 @@
1
+ .image-editor-settings-modal {
2
+ &__toggle {
3
+ position: absolute;
4
+ z-index: 100;
5
+ top: 20px;
6
+ left: 80px;
7
+ padding: 8px 12px;
8
+ border: 1px solid rgba(white, 0.2);
9
+ border-radius: 4px;
10
+ background: rgba(white, 0.1);
11
+ color: white;
12
+ cursor: pointer;
13
+ font-size: 16px;
14
+ transition: all 0.2s ease;
15
+
16
+ &:hover {
17
+ background: rgba(white, 0.2);
18
+ }
19
+
20
+ &.active {
21
+ background: rgba(white, 0.3);
22
+ }
23
+ }
24
+
25
+ &__overlay {
26
+ position: absolute;
27
+ z-index: 9999;
28
+ top: 0;
29
+ left: 0;
30
+ display: flex;
31
+ width: 100%;
32
+ height: 100%;
33
+ align-items: center;
34
+ justify-content: center;
35
+ animation: fadeIn 0.2s ease-out;
36
+ background: rgba(0, 0, 0, 0.8);
37
+ }
38
+
39
+ &__content {
40
+ display: flex;
41
+ overflow: hidden;
42
+ width: 100%;
43
+ max-height: 100%;
44
+ flex-direction: column;
45
+ animation: scaleIn 0.2s ease-out;
46
+ background: #2b2a3050;
47
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
48
+ }
49
+
50
+ &__header {
51
+ display: flex;
52
+ align-items: center;
53
+ justify-content: space-between;
54
+ padding: 20px 24px;
55
+ border-bottom: 1px solid rgba(white, 0.1);
56
+ background: #1f1e24;
57
+ }
58
+
59
+ &__title {
60
+ margin: 0;
61
+ color: white;
62
+ font-size: 20px;
63
+ font-weight: 600;
64
+ }
65
+
66
+ &__close {
67
+ padding: 4px 8px;
68
+ border: none;
69
+ border-radius: 4px;
70
+ background: transparent;
71
+ color: rgba(white, 0.7);
72
+ cursor: pointer;
73
+ font-size: 24px;
74
+ transition: all 0.2s ease;
75
+
76
+ &:hover {
77
+ background: rgba(white, 0.1);
78
+ color: white;
79
+ }
80
+ }
81
+
82
+ &__body {
83
+ flex: 1;
84
+ padding: 24px;
85
+ overflow-y: auto;
86
+ }
87
+
88
+ &__footer {
89
+ padding: 16px 24px;
90
+ border-top: 1px solid rgba(white, 0.1);
91
+ background: #1f1e24;
92
+ }
93
+
94
+ &__apply {
95
+ width: 100%;
96
+ padding: 12px 20px;
97
+ border: none;
98
+ border-radius: 6px;
99
+ background: #4caf50;
100
+ color: white;
101
+ cursor: pointer;
102
+ font-size: 16px;
103
+ font-weight: 500;
104
+ transition: all 0.2s ease;
105
+
106
+ &:hover {
107
+ background: #45a049;
108
+ }
109
+ }
110
+
111
+ &__section {
112
+ margin-bottom: 20px;
113
+ }
114
+
115
+ &__label {
116
+ display: block;
117
+ margin-bottom: 8px;
118
+ color: rgba(white, 0.9);
119
+ font-size: 14px;
120
+ font-weight: 500;
121
+
122
+ input[type='checkbox'] {
123
+ margin-right: 8px;
124
+ }
125
+ }
126
+
127
+ &__select {
128
+ width: 100%;
129
+ padding: 10px 14px;
130
+ border: 1px solid rgba(white, 0.2);
131
+ border-radius: 6px;
132
+ background: #1f1e24;
133
+ color: white;
134
+ cursor: pointer;
135
+ font-size: 14px;
136
+
137
+ option {
138
+ background: #2b2a30;
139
+ color: white;
140
+ }
141
+
142
+ &:focus {
143
+ border-color: #4caf50;
144
+ outline: none;
145
+ }
146
+ }
147
+
148
+ &__input {
149
+ width: 100%;
150
+ padding: 10px 14px;
151
+ border: 1px solid rgba(white, 0.2);
152
+ border-radius: 6px;
153
+ -moz-appearance: textfield;
154
+ appearance: textfield;
155
+ background: transparent;
156
+ color: white;
157
+ font-size: 14px;
158
+ &::-webkit-inner-spin-button {
159
+ margin: 0;
160
+ -webkit-appearance: none;
161
+ }
162
+
163
+ &::placeholder {
164
+ color: rgba(white, 0.5);
165
+ }
166
+
167
+ &:focus {
168
+ border-color: #4caf50;
169
+ outline: none;
170
+ }
171
+ }
172
+
173
+ &__checkbox {
174
+ accent-color: #4caf50;
175
+ transform: scale(1.2);
176
+ }
177
+
178
+ &__range {
179
+ width: 100%;
180
+ height: 6px;
181
+ border-radius: 3px;
182
+ background: rgba(white, 0.2);
183
+ cursor: pointer;
184
+ outline: none;
185
+
186
+ &::-webkit-slider-thumb {
187
+ width: 20px;
188
+ height: 20px;
189
+ border-radius: 50%;
190
+ appearance: none;
191
+ background: #4caf50;
192
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
193
+ cursor: pointer;
194
+ transition: all 0.2s ease;
195
+
196
+ &:hover {
197
+ transform: scale(1.1);
198
+ }
199
+ }
200
+
201
+ &::-moz-range-thumb {
202
+ width: 20px;
203
+ height: 20px;
204
+ border: none;
205
+ border-radius: 50%;
206
+ background: #4caf50;
207
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
208
+ cursor: pointer;
209
+ }
210
+ }
211
+
212
+ &__row {
213
+ display: grid;
214
+ gap: 12px;
215
+ grid-template-columns: 1fr 1fr;
216
+ }
217
+
218
+ &__divider {
219
+ height: 1px;
220
+ margin: 24px 0;
221
+ background: rgba(white, 0.1);
222
+ }
223
+
224
+ &__subtitle {
225
+ margin: 0 0 16px 0;
226
+ color: rgba(white, 0.9);
227
+ font-size: 16px;
228
+ font-weight: 600;
229
+ }
230
+
231
+ @keyframes fadeIn {
232
+ from {
233
+ opacity: 0;
234
+ }
235
+ to {
236
+ opacity: 1;
237
+ }
238
+ }
239
+
240
+ @keyframes scaleIn {
241
+ from {
242
+ opacity: 0;
243
+ transform: scale(0.9);
244
+ }
245
+ to {
246
+ opacity: 1;
247
+ transform: scale(1);
248
+ }
249
+ }
250
+
251
+ @media (max-width: 768px) {
252
+ &__body {
253
+ padding: 20px;
254
+ }
255
+
256
+ &__header,
257
+ &__footer {
258
+ padding: 16px 20px;
259
+ }
260
+
261
+ &__row {
262
+ gap: 8px;
263
+ grid-template-columns: 1fr;
264
+ }
265
+
266
+ &__icon-scroll {
267
+ gap: 12px;
268
+ }
269
+
270
+ &__icon-option {
271
+ min-width: 70px;
272
+ padding: 10px 6px 6px;
273
+
274
+ svg {
275
+ width: 28px;
276
+ height: 28px;
277
+ margin-bottom: 6px;
278
+ }
279
+
280
+ span {
281
+ font-size: 11px;
282
+ }
283
+ }
284
+ }
285
+
286
+ &__icon-scroll {
287
+ display: flex;
288
+ padding: 8px 0;
289
+ margin-top: 12px;
290
+ gap: 16px;
291
+ overflow-x: auto;
292
+ scrollbar-color: rgba(white, 0.3) rgba(white, 0.1);
293
+ scrollbar-width: thin;
294
+
295
+ &::-webkit-scrollbar {
296
+ height: 6px;
297
+ }
298
+
299
+ &::-webkit-scrollbar-track {
300
+ border-radius: 3px;
301
+ background: rgba(white, 0.1);
302
+ }
303
+
304
+ &::-webkit-scrollbar-thumb {
305
+ border-radius: 3px;
306
+ background: rgba(white, 0.3);
307
+ }
308
+ }
309
+
310
+ &__icon-option {
311
+ display: flex;
312
+ min-width: 80px;
313
+ flex-direction: column;
314
+ align-items: center;
315
+ padding: 12px 8px 8px;
316
+ border-radius: 8px;
317
+ cursor: pointer;
318
+ transition: all 0.2s ease;
319
+
320
+ svg {
321
+ width: 32px;
322
+ height: 32px;
323
+ margin-bottom: 8px;
324
+ color: rgba(white, 0.6);
325
+ transition: all 0.2s ease;
326
+ }
327
+
328
+ span {
329
+ color: rgba(white, 0.7);
330
+ font-size: 12px;
331
+ font-weight: 500;
332
+ text-align: center;
333
+ white-space: nowrap;
334
+ }
335
+
336
+ &:hover {
337
+ background: rgba(white, 0.05);
338
+
339
+ svg {
340
+ color: rgba(white, 0.9);
341
+ transform: scale(1.1);
342
+ }
343
+
344
+ span {
345
+ color: rgba(white, 0.9);
346
+ }
347
+ }
348
+
349
+ &.active {
350
+ svg {
351
+ color: #4caf50;
352
+ }
353
+
354
+ span {
355
+ color: #4caf50;
356
+ }
357
+
358
+ &:hover {
359
+ svg {
360
+ color: #66bb6a;
361
+ }
362
+
363
+ span {
364
+ color: #66bb6a;
365
+ }
366
+ }
367
+ }
368
+ }
369
+ }