@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.
- package/.changelog.draft +19 -0
- package/.release-it.json +34 -0
- package/CHANGELOG.md +27 -0
- package/README.md +209 -0
- package/babel.config.js +17 -0
- package/locales/de/LC_MESSAGES/volto.po +187 -0
- package/locales/en/LC_MESSAGES/volto.po +187 -0
- package/locales/es/LC_MESSAGES/volto.po +194 -0
- package/locales/pt_BR/LC_MESSAGES/volto.po +192 -0
- package/locales/volto.pot +189 -0
- package/news/.gitkeep +0 -0
- package/package.json +50 -0
- package/public/.gitkeep +0 -0
- package/src/components/.gitkeep +0 -0
- package/src/components/ImageEditor/ImageEditor.scss +59 -0
- package/src/components/ImageEditor/ImageEditor.tsx +290 -0
- package/src/components/ImageEditor/ImageEditorWrapper.scss +13 -0
- package/src/components/ImageEditor/ImageEditorWrapper.tsx +91 -0
- package/src/components/ImageEditor/components/AdjustableCropperBackground.tsx +60 -0
- package/src/components/ImageEditor/components/AdjustableImage.scss +3 -0
- package/src/components/ImageEditor/components/AdjustableImage.tsx +85 -0
- package/src/components/ImageEditor/components/AdjustablePreviewBackground.tsx +59 -0
- package/src/components/ImageEditor/components/Button.scss +30 -0
- package/src/components/ImageEditor/components/Button.tsx +27 -0
- package/src/components/ImageEditor/components/Navigation.scss +38 -0
- package/src/components/ImageEditor/components/Navigation.tsx +153 -0
- package/src/components/ImageEditor/components/SettingsModal.scss +369 -0
- package/src/components/ImageEditor/components/SettingsModal.tsx +582 -0
- package/src/components/ImageEditor/components/Slider.scss +231 -0
- package/src/components/ImageEditor/components/Slider.tsx +322 -0
- package/src/components/ImageEditor/types/ImageSettings.ts +23 -0
- package/src/components/Widgets/FileWidget.scss +14 -0
- package/src/components/Widgets/FileWidget.tsx +239 -0
- package/src/config/settings.ts +5 -0
- package/src/customizations/volto/components/manage/Widgets/FileWidget.jsx +3 -0
- package/src/icons/AspectRatioIcons.tsx +39 -0
- package/src/icons/BrightnessIcon.tsx +19 -0
- package/src/icons/ContrastIcon.tsx +19 -0
- package/src/icons/CropIcon.tsx +19 -0
- package/src/icons/DownloadIcon.tsx +20 -0
- package/src/icons/FlipHorizontalIcon.tsx +21 -0
- package/src/icons/FlipVerticalIcon.tsx +27 -0
- package/src/icons/HueIcon.tsx +20 -0
- package/src/icons/ResetIcon.tsx +19 -0
- package/src/icons/ResetRotationIcon.tsx +20 -0
- package/src/icons/RestrictionIcons.tsx +19 -0
- package/src/icons/RotateLeftIcon.tsx +21 -0
- package/src/icons/RotateRightIcon.tsx +21 -0
- package/src/icons/SaturationIcon.tsx +20 -0
- package/src/icons/SettingsIcon.tsx +21 -0
- package/src/icons/StencilIcons.tsx +8 -0
- package/src/index.ts +10 -0
- package/src/theme/constants.scss +13 -0
- package/src/theme/custom.css +82 -0
- package/src/theme/mixins.scss +91 -0
- package/towncrier.toml +33 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileWidget component.
|
|
3
|
+
* @module components/manage/Widgets/FileWidget
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { Button, Dimmer, Grid } from 'semantic-ui-react';
|
|
8
|
+
import { readAsDataURL } from 'promise-file-reader';
|
|
9
|
+
import { injectIntl } from 'react-intl';
|
|
10
|
+
import deleteSVG from '@plone/volto/icons/delete.svg';
|
|
11
|
+
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
12
|
+
import Toast from '@plone/volto/components/manage/Toast/Toast';
|
|
13
|
+
import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
|
|
14
|
+
import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
|
|
15
|
+
import loadable from '@loadable/component';
|
|
16
|
+
import { validateFileUploadSize } from '@plone/volto/helpers/FormValidation/FormValidation';
|
|
17
|
+
import { defineMessages, useIntl } from 'react-intl';
|
|
18
|
+
import Image from '@plone/volto/components/theme/Image/Image';
|
|
19
|
+
import { toast } from 'react-toastify';
|
|
20
|
+
import ImageEditorWrapper from '../ImageEditor/ImageEditorWrapper';
|
|
21
|
+
import './FileWidget.scss';
|
|
22
|
+
import { useUpdateEffect } from 'react-advanced-cropper';
|
|
23
|
+
|
|
24
|
+
const imageMimetypes = [
|
|
25
|
+
'image/png',
|
|
26
|
+
'image/jpeg',
|
|
27
|
+
'image/webp',
|
|
28
|
+
'image/jpg',
|
|
29
|
+
'image/gif',
|
|
30
|
+
'image/svg+xml',
|
|
31
|
+
];
|
|
32
|
+
const Dropzone = loadable(() => import('react-dropzone'));
|
|
33
|
+
|
|
34
|
+
const messages = defineMessages({
|
|
35
|
+
releaseDrag: {
|
|
36
|
+
id: 'Drop files here ...',
|
|
37
|
+
defaultMessage: 'Drop files here ...',
|
|
38
|
+
},
|
|
39
|
+
editFile: {
|
|
40
|
+
id: 'Drop file here to replace the existing file',
|
|
41
|
+
defaultMessage: 'Drop file here to replace the existing file',
|
|
42
|
+
},
|
|
43
|
+
fileDrag: {
|
|
44
|
+
id: 'Drop file here to upload a new file',
|
|
45
|
+
defaultMessage: 'Drop file here to upload a new file',
|
|
46
|
+
},
|
|
47
|
+
replaceFile: {
|
|
48
|
+
id: 'Replace existing file',
|
|
49
|
+
defaultMessage: 'Replace existing file',
|
|
50
|
+
},
|
|
51
|
+
addNewFile: {
|
|
52
|
+
id: 'Choose a file',
|
|
53
|
+
defaultMessage: 'Choose a file',
|
|
54
|
+
},
|
|
55
|
+
maxSizeError: {
|
|
56
|
+
id: 'The file you uploaded exceeded the maximum allowed size of {size} bytes',
|
|
57
|
+
defaultMessage:
|
|
58
|
+
'The file you uploaded exceeded the maximum allowed size of {size} bytes',
|
|
59
|
+
},
|
|
60
|
+
acceptError: {
|
|
61
|
+
id: 'File is not of the accepted type {accept}',
|
|
62
|
+
defaultMessage: 'File is not of the accepted type {accept}',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const FileWidget = (props) => {
|
|
67
|
+
const { id, value, onChange, isDisabled } = props;
|
|
68
|
+
const [data, setData] = React.useState(null);
|
|
69
|
+
const isImage = value && imageMimetypes.includes(value['content-type']);
|
|
70
|
+
const mainColumnWidth = isImage ? 10 : 12;
|
|
71
|
+
const intl = useIntl();
|
|
72
|
+
|
|
73
|
+
const imgAttrs = React.useMemo(() => {
|
|
74
|
+
const imgObj = {};
|
|
75
|
+
if (value?.download) {
|
|
76
|
+
imgObj.item = {
|
|
77
|
+
'@id': value.download.substring(0, value.download.indexOf('/@@images')),
|
|
78
|
+
image: value,
|
|
79
|
+
};
|
|
80
|
+
} else if (value?.data) {
|
|
81
|
+
imgObj.src = `data:${value['content-type']};${value.encoding},${value.data}`;
|
|
82
|
+
}
|
|
83
|
+
return imgObj;
|
|
84
|
+
}, [value]);
|
|
85
|
+
|
|
86
|
+
useUpdateEffect(() => {
|
|
87
|
+
if (data) {
|
|
88
|
+
onChange(id, data);
|
|
89
|
+
}
|
|
90
|
+
}, [data]);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Drop handler
|
|
94
|
+
* @method onDrop
|
|
95
|
+
* @param {array} files File objects
|
|
96
|
+
* @returns {undefined}
|
|
97
|
+
*/
|
|
98
|
+
const onDrop = (files, rejectedFiles) => {
|
|
99
|
+
rejectedFiles.forEach((file) => {
|
|
100
|
+
file.errors.forEach((err) => {
|
|
101
|
+
if (err.code === 'file-too-large') {
|
|
102
|
+
toast.error(
|
|
103
|
+
<Toast
|
|
104
|
+
error
|
|
105
|
+
title={intl.formatMessage(messages.maxSizeError, {
|
|
106
|
+
size: props.size,
|
|
107
|
+
})}
|
|
108
|
+
/>,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (err.code === 'file-invalid-type') {
|
|
113
|
+
toast.error(
|
|
114
|
+
<Toast
|
|
115
|
+
error
|
|
116
|
+
title={intl.formatMessage(messages.acceptError, {
|
|
117
|
+
accept: props.accept,
|
|
118
|
+
})}
|
|
119
|
+
/>,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
if (files.length < 1) return;
|
|
125
|
+
const file = files[0];
|
|
126
|
+
if (!validateFileUploadSize(file, intl.formatMessage)) return;
|
|
127
|
+
readAsDataURL(file).then((data) => {
|
|
128
|
+
const fields = data.match(/^data:(.*);(.*),(.*)$/);
|
|
129
|
+
onChange(id, {
|
|
130
|
+
data: fields[3],
|
|
131
|
+
encoding: fields[2],
|
|
132
|
+
'content-type': fields[1],
|
|
133
|
+
filename: file.name,
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
let reader = new FileReader();
|
|
138
|
+
reader.onload = function () {
|
|
139
|
+
const fields = reader.result.match(/^data:(.*);(.*),(.*)$/);
|
|
140
|
+
if (imageMimetypes.includes(fields[1])) {
|
|
141
|
+
let imagePreview = document.getElementById(`field-${id}-image`);
|
|
142
|
+
if (imagePreview) imagePreview.src = reader.result;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
reader.readAsDataURL(files[0]);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<FormFieldWrapper {...props}>
|
|
150
|
+
<Grid className="image-widget-grid">
|
|
151
|
+
<Grid.Row stretched>
|
|
152
|
+
<Grid.Column stretched width={mainColumnWidth}>
|
|
153
|
+
<Dropzone
|
|
154
|
+
onDrop={onDrop}
|
|
155
|
+
{...(props.size ? { maxSize: props.size } : {})}
|
|
156
|
+
{...(props.accept ? { accept: props.accept } : {})}
|
|
157
|
+
>
|
|
158
|
+
{({ getRootProps, getInputProps, isDragActive }) => (
|
|
159
|
+
<div className="file-widget-dropzone" {...getRootProps()}>
|
|
160
|
+
{isDragActive && <Dimmer active></Dimmer>}
|
|
161
|
+
{isImage ? (
|
|
162
|
+
<Image
|
|
163
|
+
className="image-preview small ui image"
|
|
164
|
+
id={`field-${id}-image`}
|
|
165
|
+
{...imgAttrs}
|
|
166
|
+
/>
|
|
167
|
+
) : (
|
|
168
|
+
<div className="dropzone-placeholder">
|
|
169
|
+
{isDragActive ? (
|
|
170
|
+
<p className="dropzone-text">
|
|
171
|
+
{intl.formatMessage(messages.releaseDrag)}
|
|
172
|
+
</p>
|
|
173
|
+
) : value ? (
|
|
174
|
+
<p className="dropzone-text">
|
|
175
|
+
{intl.formatMessage(messages.editFile)}
|
|
176
|
+
</p>
|
|
177
|
+
) : (
|
|
178
|
+
<p className="dropzone-text">
|
|
179
|
+
{intl.formatMessage(messages.fileDrag)}
|
|
180
|
+
</p>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
)}
|
|
184
|
+
|
|
185
|
+
<label className="label-file-widget-input">
|
|
186
|
+
{value
|
|
187
|
+
? intl.formatMessage(messages.replaceFile)
|
|
188
|
+
: intl.formatMessage(messages.addNewFile)}
|
|
189
|
+
</label>
|
|
190
|
+
<input
|
|
191
|
+
{...getInputProps({
|
|
192
|
+
type: 'file',
|
|
193
|
+
style: { display: 'none' },
|
|
194
|
+
})}
|
|
195
|
+
id={`field-${id}`}
|
|
196
|
+
name={id}
|
|
197
|
+
type="file"
|
|
198
|
+
disabled={isDisabled}
|
|
199
|
+
/>
|
|
200
|
+
</div>
|
|
201
|
+
)}
|
|
202
|
+
</Dropzone>
|
|
203
|
+
<div className="field-file-name">
|
|
204
|
+
{value && (
|
|
205
|
+
<UniversalLink href={value.download} download={true}>
|
|
206
|
+
{value.filename}
|
|
207
|
+
</UniversalLink>
|
|
208
|
+
)}
|
|
209
|
+
{value && (
|
|
210
|
+
<>
|
|
211
|
+
<Button
|
|
212
|
+
type="button"
|
|
213
|
+
icon
|
|
214
|
+
basic
|
|
215
|
+
className="delete-button"
|
|
216
|
+
aria-label="delete file"
|
|
217
|
+
disabled={isDisabled}
|
|
218
|
+
onClick={() => {
|
|
219
|
+
onChange(id, null);
|
|
220
|
+
}}
|
|
221
|
+
>
|
|
222
|
+
<Icon name={deleteSVG} size="20px" />
|
|
223
|
+
</Button>
|
|
224
|
+
</>
|
|
225
|
+
)}
|
|
226
|
+
</div>
|
|
227
|
+
</Grid.Column>
|
|
228
|
+
{isImage && (
|
|
229
|
+
<Grid.Column stretched width="2" className={'image-editor-cta'}>
|
|
230
|
+
<ImageEditorWrapper value={value} setData={setData} />
|
|
231
|
+
</Grid.Column>
|
|
232
|
+
)}
|
|
233
|
+
</Grid.Row>
|
|
234
|
+
</Grid>
|
|
235
|
+
</FormFieldWrapper>
|
|
236
|
+
);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export default injectIntl(FileWidget);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
TbAspectRatio,
|
|
4
|
+
TbSquare,
|
|
5
|
+
TbRectangleVertical,
|
|
6
|
+
TbRectangle,
|
|
7
|
+
TbPhoto,
|
|
8
|
+
TbDeviceDesktop,
|
|
9
|
+
TbDeviceTv,
|
|
10
|
+
TbDeviceMobile,
|
|
11
|
+
TbRectangleFilled,
|
|
12
|
+
} from 'react-icons/tb';
|
|
13
|
+
|
|
14
|
+
// Free aspect ratio icon
|
|
15
|
+
export const FreeIcon = () => <TbAspectRatio />;
|
|
16
|
+
|
|
17
|
+
// Square 1:1
|
|
18
|
+
export const SquareIcon = () => <TbSquare />;
|
|
19
|
+
|
|
20
|
+
// Portrait 1:2
|
|
21
|
+
export const PortraitIcon = () => <TbRectangleVertical />;
|
|
22
|
+
|
|
23
|
+
// Landscape 2:1
|
|
24
|
+
export const LandscapeIcon = () => <TbRectangle />;
|
|
25
|
+
|
|
26
|
+
// Photo 3:4
|
|
27
|
+
export const PhotoIcon = () => <TbPhoto />;
|
|
28
|
+
|
|
29
|
+
// Monitor 4:3
|
|
30
|
+
export const MonitorIcon = () => <TbDeviceDesktop />;
|
|
31
|
+
|
|
32
|
+
// Widescreen 16:9
|
|
33
|
+
export const WidescreenIcon = () => <TbDeviceTv />;
|
|
34
|
+
|
|
35
|
+
// Mobile 9:16
|
|
36
|
+
export const MobileIcon = () => <TbDeviceMobile />;
|
|
37
|
+
|
|
38
|
+
// Ultra-wide 21:9
|
|
39
|
+
export const UltraWideIcon = () => <TbRectangleFilled />;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const BrightnessIcon: FC<Props> = ({ className }) => {
|
|
8
|
+
return (
|
|
9
|
+
<svg
|
|
10
|
+
className={className}
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
width="24"
|
|
13
|
+
height="24"
|
|
14
|
+
xmlSpace="preserve"
|
|
15
|
+
>
|
|
16
|
+
<path d="M12 15.3c-1.8 0-3.3-1.5-3.3-3.3 0-1.8 1.5-3.3 3.3-3.3 1.8 0 3.3 1.5 3.3 3.3 0 1.8-1.5 3.3-3.3 3.3zm0-5.1c-1 0-1.8.8-1.8 1.8s.8 1.8 1.8 1.8 1.8-.8 1.8-1.8-.8-1.8-1.8-1.8zM12 7.7c-.4 0-.8-.3-.8-.8V5.2c0-.4.3-.8.8-.8s.8.3.8.8V7c0 .4-.4.7-.8.7zM9 9c-.3.3-.8.3-1.1 0L6.6 7.7c-.3-.3-.3-.8 0-1.1.3-.3.8-.3 1.1 0L9 7.9c.3.3.3.8 0 1.1zM7.7 12c0 .4-.3.8-.8.8H5.2c-.4 0-.8-.3-.8-.8s.3-.8.8-.8H7c.4.1.7.4.7.8zM9 15c.3.3.3.8 0 1.1l-1.3 1.3c-.3.3-.8.3-1.1 0-.3-.3-.3-.8 0-1.1L7.9 15c.3-.3.8-.3 1.1 0zM12 16.3c.4 0 .8.3.8.8v1.8c0 .4-.3.8-.8.8s-.8-.3-.8-.8V17c.1-.4.4-.7.8-.7zM15 15c.3-.3.8-.3 1.1 0l1.3 1.3c.3.3.3.8 0 1.1s-.8.3-1.1 0L15 16.1c-.3-.3-.3-.8 0-1.1zM16.3 12c0-.4.3-.8.8-.8h1.8c.4 0 .8.3.8.8s-.3.8-.8.8H17c-.4 0-.7-.4-.7-.8zM15 9c-.3-.3-.3-.8 0-1.1l1.3-1.3c.3-.3.8-.3 1.1 0 .3.3.3.8 0 1.1L16.1 9c-.3.3-.8.3-1.1 0z" />
|
|
17
|
+
</svg>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const ContrastIcon: FC<Props> = ({ className }) => {
|
|
8
|
+
return (
|
|
9
|
+
<svg
|
|
10
|
+
className={className}
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
width="24"
|
|
13
|
+
height="24"
|
|
14
|
+
xmlSpace="preserve"
|
|
15
|
+
>
|
|
16
|
+
<path d="M12 4.4c-4.2 0-7.6 3.4-7.6 7.6s3.4 7.6 7.6 7.6 7.6-3.4 7.6-7.6-3.4-7.6-7.6-7.6zM5.9 12c0-3.3 2.7-6.1 6.1-6.1V18c-3.3.1-6.1-2.7-6.1-6z" />
|
|
17
|
+
</svg>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const CropIcon: FC<Props> = ({ className }) => {
|
|
8
|
+
return (
|
|
9
|
+
<svg
|
|
10
|
+
className={className}
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
width="24"
|
|
13
|
+
height="24"
|
|
14
|
+
xmlSpace="preserve"
|
|
15
|
+
>
|
|
16
|
+
<path d="M18.2 15.2h-1.5V7.3H8.8V5.8c0-.4-.3-.8-.8-.8s-.7.4-.7.8v1.5H5.8c-.4 0-.8.3-.8.8s.3.8.8.8h1.5v7.8h7.8v1.5c0 .4.3.8.8.8s.8-.3.8-.8v-1.5h1.5c.4 0 .8-.3.8-.8s-.4-.7-.8-.7zM8.8 8.8h6.3v6.3H8.8V8.8z" />
|
|
17
|
+
</svg>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const DownloadIcon: FC<Props> = ({ className }) => {
|
|
8
|
+
return (
|
|
9
|
+
<svg
|
|
10
|
+
className={className}
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
width="24"
|
|
13
|
+
height="24"
|
|
14
|
+
xmlSpace="preserve"
|
|
15
|
+
>
|
|
16
|
+
<path d="M17.2 12.1c-.4 0-.8.3-.8.8v2.8c0 .6-.4 1-1 1H8.6c-.6 0-1-.4-1-1v-2.8c0-.4-.3-.8-.8-.8s-.8.3-.8.8v2.8c0 1.4 1.1 2.5 2.5 2.5h6.8c1.4 0 2.5-1.1 2.5-2.5v-2.8c.1-.5-.2-.8-.6-.8z" />
|
|
17
|
+
<path d="M11.5 14.6c.1.1.3.2.5.2s.4-.1.5-.2l2.4-2.3c.3-.3.3-.8 0-1.1-.3-.3-.8-.3-1.1 0l-1.1 1.1V6.6c0-.4-.3-.8-.8-.8s-.8.3-.8.8v5.6l-1.1-1c-.3-.3-.8-.3-1.1 0-.3.3-.3.8 0 1.1l2.6 2.3z" />
|
|
18
|
+
</svg>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const FlipHorizontalIcon: FC<Props> = ({ className }) => {
|
|
8
|
+
return (
|
|
9
|
+
<svg
|
|
10
|
+
className={className}
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
width="16"
|
|
13
|
+
height="16"
|
|
14
|
+
>
|
|
15
|
+
<path
|
|
16
|
+
fill="#FFF"
|
|
17
|
+
d="M8 15.9c-.6 0-1-.4-1-1V1c0-.6.4-1 1-1s1 .4 1 1v13.9c0 .6-.4 1-1 1zm4-2.4h-.5c-.4 0-.8-.3-.8-.8s.3-.8.8-.8h.5c.4 0 .8.3.8.8s-.4.8-.8.8zm2.7 0h-.5c-.4 0-.8-.3-.8-.8 0-.3.2-.6.5-.7.1-.3.4-.5.7-.5.4 0 .8.3.8.8v.5c.1.4-.3.7-.7.7zm0-2.8c-.4 0-.8-.3-.8-.8v-.8c0-.4.3-.8.8-.8s.8.3.8.8v.8c0 .4-.4.8-.8.8zm0-3.3c-.4 0-.8-.3-.8-.8v-.7c0-.4.3-.8.8-.8s.8.3.8.8v.8c0 .4-.4.7-.8.7zm0-3.3c-.3 0-.6-.2-.7-.5-.3-.1-.5-.4-.5-.7 0-.4.3-.8.8-.8h.5c.4 0 .8.3.8.8v.5c-.1.4-.5.7-.9.7zM12 3.6h-.5c-.4 0-.8-.3-.8-.8s.3-.8.8-.8h.5c.4 0 .8.3.8.8s-.4.8-.8.8zM4.5 13.5H1.3c-.4 0-.8-.3-.8-.8V2.9c0-.4.3-.8.8-.8h3.2v1.5H2V12h2.5v1.5z"
|
|
18
|
+
/>
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const FlipVerticalIcon: FC<Props> = ({ className }) => {
|
|
8
|
+
return (
|
|
9
|
+
<svg
|
|
10
|
+
className={className}
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
width="16"
|
|
13
|
+
height="16"
|
|
14
|
+
>
|
|
15
|
+
<path
|
|
16
|
+
fill="#FFF"
|
|
17
|
+
d="M15 9H1c-.5 0-1-.5-1-1s.4-1 1-1h14c.6 0 1 .4 1 1s-.5 1-1 1zM3.1 12.7c-.4 0-.8-.3-.8-.8v-.5c0-.4.3-.8.8-.8s.8.3.8.8v.6c0 .4-.3.7-.8.7zM3.6 15.4h-.5c-.4 0-.8-.3-.8-.8v-.5c0-.4.3-.8.8-.8.3 0 .6.2.7.5.3.1.5.4.5.7.1.6-.2.9-.7.9zM10.1 15.4h-.8c-.4 0-.8-.3-.8-.8s.3-.8.8-.8h.8c.4 0 .8.3.8.8s-.4.8-.8.8zm-3.2 0H6c-.4 0-.8-.3-.8-.8s.3-.8.8-.8h.8c.4 0 .8.3.8.8s-.3.8-.7.8zM13 15.4h-.5c-.4 0-.8-.3-.8-.8 0-.3.2-.6.5-.7.1-.3.4-.5.7-.5.4 0 .8.3.8.8v.5c.1.4-.2.7-.7.7zM13 12.7c-.4 0-.8-.3-.8-.8v-.5c0-.4.3-.8.8-.8s.8.3.8.8v.6c0 .4-.3.7-.8.7z"
|
|
18
|
+
/>
|
|
19
|
+
<g>
|
|
20
|
+
<path
|
|
21
|
+
fill="#FFF"
|
|
22
|
+
d="M13.8 4.5h-1.5V2H3.9v2.5H2.4V1.3c0-.4.3-.8.8-.8H13c.4 0 .8.3.8.8v3.2z"
|
|
23
|
+
/>
|
|
24
|
+
</g>
|
|
25
|
+
</svg>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const HueIcon: FC<Props> = ({ className }) => {
|
|
8
|
+
return (
|
|
9
|
+
<svg
|
|
10
|
+
className={className}
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
width="24"
|
|
13
|
+
height="24"
|
|
14
|
+
xmlSpace="preserve"
|
|
15
|
+
>
|
|
16
|
+
<path d="M12 19.6c-4.2 0-7.6-3.4-7.6-7.6S7.8 4.4 12 4.4s7.6 3.4 7.6 7.6-3.4 7.6-7.6 7.6zm0-13.7c-3.3 0-6.1 2.7-6.1 6.1s2.7 6.1 6.1 6.1 6.1-2.7 6.1-6.1-2.8-6.1-6.1-6.1z" />
|
|
17
|
+
<path d="M12 15.3c-1.8 0-3.3-1.5-3.3-3.3 0-1.8 1.5-3.3 3.3-3.3 1.8 0 3.3 1.5 3.3 3.3 0 1.8-1.5 3.3-3.3 3.3zm0-5.1c-1 0-1.8.8-1.8 1.8s.8 1.8 1.8 1.8 1.8-.8 1.8-1.8-.8-1.8-1.8-1.8z" />
|
|
18
|
+
</svg>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const ResetIcon: FC<Props> = ({ className }) => {
|
|
8
|
+
return (
|
|
9
|
+
<svg
|
|
10
|
+
className={className}
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
width="24"
|
|
13
|
+
height="24"
|
|
14
|
+
xmlSpace="preserve"
|
|
15
|
+
>
|
|
16
|
+
<path d="M21.2 9c-.3-.2-.8-.1-1 .2l-.8 1.3c-.7-3.4-3.8-6-7.4-6-4.2 0-7.6 3.4-7.6 7.6s3.4 7.6 7.6 7.6c2.3 0 4.5-1.1 6-2.9.3-.3.2-.8-.1-1.1-.3-.3-.8-.2-1.1.1-1.2 1.5-2.9 2.3-4.8 2.3-3.3 0-6.1-2.7-6.1-6.1S8.7 5.9 12 5.9c2.9 0 5.4 2.1 5.9 4.9l-1.3-.8c-.4-.2-.8-.1-1 .3-.2.4-.1.8.3 1l2.9 1.6c.1.1.2.1.4.1.3 0 .5-.1.6-.4l1.7-2.8c.2-.1.1-.6-.3-.8z" />
|
|
17
|
+
</svg>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export const ResetRotationIcon = () => (
|
|
4
|
+
<svg
|
|
5
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
+
viewBox="0 0 24 24"
|
|
7
|
+
fill="none"
|
|
8
|
+
stroke="currentColor"
|
|
9
|
+
strokeWidth={2}
|
|
10
|
+
strokeLinecap="round"
|
|
11
|
+
strokeLinejoin="round"
|
|
12
|
+
width="18"
|
|
13
|
+
height="18"
|
|
14
|
+
>
|
|
15
|
+
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" />
|
|
16
|
+
<path d="M21 3v5h-5" />
|
|
17
|
+
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" />
|
|
18
|
+
<path d="M3 21v-5h5" />
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
TbAdjustmentsHorizontal,
|
|
4
|
+
TbMaximize,
|
|
5
|
+
TbFocus2,
|
|
6
|
+
TbUnlink,
|
|
7
|
+
} from 'react-icons/tb';
|
|
8
|
+
|
|
9
|
+
// Fit Area - image fits inside boundaries
|
|
10
|
+
export const FitAreaIcon = () => <TbAdjustmentsHorizontal />;
|
|
11
|
+
|
|
12
|
+
// Fill Area - image fills the boundaries
|
|
13
|
+
export const FillAreaIcon = () => <TbMaximize />;
|
|
14
|
+
|
|
15
|
+
// Stencil - image restricted to stencil area
|
|
16
|
+
export const StencilIcon = () => <TbFocus2 />;
|
|
17
|
+
|
|
18
|
+
// None - no restrictions
|
|
19
|
+
export const NoRestrictionIcon = () => <TbUnlink />;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const RotateLeftIcon: FC<Props> = ({ className }) => {
|
|
8
|
+
return (
|
|
9
|
+
<svg
|
|
10
|
+
className={className}
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
width="16"
|
|
13
|
+
height="16"
|
|
14
|
+
>
|
|
15
|
+
<path
|
|
16
|
+
fill="#FFF"
|
|
17
|
+
d="M3.5 11.8c-.2.2-.2.6 0 .9 1.1 1.1 2.8 1.8 4.5 1.8 3.7 0 6.5-2.8 6.5-6.5s-3-6.5-6.5-6.5c-1.7 0-3.3.7-4.5 1.8l-1-1c-.4-.4-1-.1-1 .4v3.8c0 .3.3.6.6.6h3.7c.5 0 .8-.7.4-1L5 4.9c.9-.9 1.9-1.3 3-1.3 3 0 5.4 3.1 4 6.2-.7 1.6-2.3 2.6-4 2.6-1.2 0-2.2-.4-3-1.3-.2-.2-.6-.2-.8 0l-.7.7z"
|
|
18
|
+
/>
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const RotateRightIcon: FC<Props> = ({ className }) => {
|
|
8
|
+
return (
|
|
9
|
+
<svg
|
|
10
|
+
className={className}
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
width="16"
|
|
13
|
+
height="16"
|
|
14
|
+
>
|
|
15
|
+
<path
|
|
16
|
+
fill="#FFF"
|
|
17
|
+
d="M12.5 11.8c.2.2.2.6 0 .9-1.1 1.1-2.8 1.8-4.5 1.8-3.7 0-6.5-2.8-6.5-6.5s3-6.5 6.5-6.5c1.7 0 3.3.7 4.5 1.8l1-1c.4-.4 1-.1 1 .4v3.8c0 .3-.3.6-.6.6h-3.7c-.5 0-.8-.7-.4-1L11 4.9c-.9-.9-1.9-1.3-3-1.3-3 0-5.4 3.1-4 6.2.7 1.6 2.3 2.6 4 2.6 1.2 0 2.2-.4 3-1.3.2-.2.6-.2.8 0l.7.7z"
|
|
18
|
+
/>
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const SaturationIcon: FC<Props> = ({ className }) => {
|
|
8
|
+
return (
|
|
9
|
+
<svg
|
|
10
|
+
className={className}
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
width="24"
|
|
13
|
+
height="24"
|
|
14
|
+
xmlSpace="preserve"
|
|
15
|
+
>
|
|
16
|
+
<path d="M14.2 16c.5-.5.8-1.2.8-2 0-.3 0-.6-.1-.9-.3.1-.5.1-.8.1-1.3 0-2.2-.7-2.9-1.3-.2-.2-.8-.6-.9-.6 0 0-.1 0-.3.1-.5 1-.8 1.8-.8 2.5s.3 1.4.8 2c1 1.3 3 1.3 4.2.1z" />
|
|
17
|
+
<path d="M12 19.7c-1.5 0-3-.6-4.1-1.7-1.1-1.1-1.6-2.5-1.6-4 .1-2.7 1.8-5 3.6-7.4.5-.6 1-1.3 1.4-2l.7-.8.6.9c.5.7 1 1.4 1.5 2 1.8 2.4 3.5 4.6 3.6 7.3 0 1.5-.5 2.9-1.6 4-1.1 1-2.6 1.7-4.1 1.7zm0-13.3-.9 1.2C9.5 9.7 7.9 11.8 7.8 14c0 1.1.4 2.1 1.1 2.9.8.8 1.9 1.3 3 1.3s2.3-.5 3-1.3c.8-.8 1.2-1.8 1.1-2.9-.1-2.1-1.5-4.1-3.3-6.4-.1-.4-.4-.8-.7-1.2z" />
|
|
18
|
+
</svg>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export const SettingsIcon = () => (
|
|
4
|
+
<svg
|
|
5
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
+
viewBox="0 0 24 24"
|
|
7
|
+
fill="none"
|
|
8
|
+
stroke="currentColor"
|
|
9
|
+
strokeWidth={2}
|
|
10
|
+
strokeLinecap="round"
|
|
11
|
+
strokeLinejoin="round"
|
|
12
|
+
width="18"
|
|
13
|
+
height="18"
|
|
14
|
+
>
|
|
15
|
+
<circle cx="12" cy="12" r="3" />
|
|
16
|
+
<path d="M12 1v6m0 6v6" />
|
|
17
|
+
<path d="M12 6.5a5.5 5.5 0 0 1 0 11" />
|
|
18
|
+
<path d="M1 12h6m6 0h6" />
|
|
19
|
+
<path d="M17.5 12a5.5 5.5 0 0 1-11 0" />
|
|
20
|
+
</svg>
|
|
21
|
+
);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
$maximum: 1200px;
|
|
2
|
+
$desktop: 920px;
|
|
3
|
+
$tablet: 768px;
|
|
4
|
+
$mobile: 540px;
|
|
5
|
+
|
|
6
|
+
$container-padding: 24px;
|
|
7
|
+
|
|
8
|
+
$dark-theme-color: #6fcff0;
|
|
9
|
+
$theme-color: #61dafb;
|
|
10
|
+
$black-color: #212121;
|
|
11
|
+
$black-button-color: #36393f;
|
|
12
|
+
$black-button-color-hover: #20232a;
|
|
13
|
+
$real-theme-color: #1aa7f9;
|