@capillarytech/creatives-library 8.0.267 → 8.0.269
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/constants/unified.js +1 -0
- package/package.json +1 -1
- package/services/api.js +5 -0
- package/utils/common.js +6 -0
- package/utils/imageUrlUpload.js +141 -0
- package/utils/tagValidations.js +2 -1
- package/utils/tests/transformerUtils.test.js +297 -0
- package/utils/transformerUtils.js +40 -0
- package/v2Components/CapImageUpload/constants.js +2 -0
- package/v2Components/CapImageUpload/index.js +65 -16
- package/v2Components/CapImageUpload/index.scss +4 -1
- package/v2Components/CapImageUpload/messages.js +5 -1
- package/v2Components/CapImageUrlUpload/constants.js +26 -0
- package/v2Components/CapImageUrlUpload/index.js +365 -0
- package/v2Components/CapImageUrlUpload/index.scss +35 -0
- package/v2Components/CapImageUrlUpload/messages.js +47 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +2 -2
- package/v2Components/CommonTestAndPreview/index.js +4 -15
- package/v2Components/FormBuilder/index.js +8 -8
- package/v2Containers/App/constants.js +5 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +57 -2
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +1 -0
- package/v2Containers/CreativesContainer/constants.js +3 -0
- package/v2Containers/CreativesContainer/index.js +168 -0
- package/v2Containers/CreativesContainer/messages.js +4 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +210 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +304 -0
- package/v2Containers/FTP/index.js +1 -1
- package/v2Containers/InApp/index.js +1 -0
- package/v2Containers/Line/Container/Text/index.js +1 -0
- package/v2Containers/MobilePushNew/index.js +1 -0
- package/v2Containers/Rcs/index.js +3 -0
- package/v2Containers/SmsTrai/Edit/index.js +2 -12
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +36 -648
- package/v2Containers/Templates/ChannelTypeIllustration.js +13 -1
- package/v2Containers/Templates/_templates.scss +205 -0
- package/v2Containers/Templates/actions.js +2 -1
- package/v2Containers/Templates/constants.js +1 -0
- package/v2Containers/Templates/index.js +274 -34
- package/v2Containers/Templates/messages.js +24 -0
- package/v2Containers/Templates/reducer.js +2 -0
- package/v2Containers/Templates/tests/index.test.js +10 -0
- package/v2Containers/TemplatesV2/index.js +15 -7
- package/v2Containers/TemplatesV2/messages.js +4 -0
- package/v2Containers/Viber/index.js +1 -0
- package/v2Containers/WebPush/Create/components/BrandIconSection.js +108 -0
- package/v2Containers/WebPush/Create/components/ButtonForm.js +172 -0
- package/v2Containers/WebPush/Create/components/ButtonItem.js +101 -0
- package/v2Containers/WebPush/Create/components/ButtonList.js +145 -0
- package/v2Containers/WebPush/Create/components/ButtonsLinksSection.js +164 -0
- package/v2Containers/WebPush/Create/components/ButtonsLinksSection.test.js +463 -0
- package/v2Containers/WebPush/Create/components/FormActions.js +54 -0
- package/v2Containers/WebPush/Create/components/FormActions.test.js +163 -0
- package/v2Containers/WebPush/Create/components/MediaSection.js +142 -0
- package/v2Containers/WebPush/Create/components/MediaSection.test.js +341 -0
- package/v2Containers/WebPush/Create/components/MessageSection.js +103 -0
- package/v2Containers/WebPush/Create/components/MessageSection.test.js +268 -0
- package/v2Containers/WebPush/Create/components/NotificationTitleSection.js +87 -0
- package/v2Containers/WebPush/Create/components/NotificationTitleSection.test.js +210 -0
- package/v2Containers/WebPush/Create/components/TemplateNameSection.js +54 -0
- package/v2Containers/WebPush/Create/components/TemplateNameSection.test.js +143 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/ButtonsLinksSection.test.js.snap +86 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/FormActions.test.js.snap +16 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/MediaSection.test.js.snap +41 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/MessageSection.test.js.snap +54 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/NotificationTitleSection.test.js.snap +37 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/TemplateNameSection.test.js.snap +21 -0
- package/v2Containers/WebPush/Create/components/_buttons.scss +246 -0
- package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +554 -0
- package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +607 -0
- package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +633 -0
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +666 -0
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +74 -0
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +78 -0
- package/v2Containers/WebPush/Create/hooks/useButtonManagement.js +138 -0
- package/v2Containers/WebPush/Create/hooks/useButtonManagement.test.js +406 -0
- package/v2Containers/WebPush/Create/hooks/useCharacterCount.js +30 -0
- package/v2Containers/WebPush/Create/hooks/useCharacterCount.test.js +151 -0
- package/v2Containers/WebPush/Create/hooks/useImageUpload.js +104 -0
- package/v2Containers/WebPush/Create/hooks/useImageUpload.test.js +538 -0
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +122 -0
- package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +633 -0
- package/v2Containers/WebPush/Create/index.js +1148 -0
- package/v2Containers/WebPush/Create/index.scss +134 -0
- package/v2Containers/WebPush/Create/messages.js +211 -0
- package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +228 -0
- package/v2Containers/WebPush/Create/preview/NotificationContainer.js +294 -0
- package/v2Containers/WebPush/Create/preview/PreviewContent.js +90 -0
- package/v2Containers/WebPush/Create/preview/PreviewControls.js +305 -0
- package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +25 -0
- package/v2Containers/WebPush/Create/preview/WebPushPreview.js +156 -0
- package/v2Containers/WebPush/Create/preview/assets/Light.svg +53 -0
- package/v2Containers/WebPush/Create/preview/assets/Top.svg +5 -0
- package/v2Containers/WebPush/Create/preview/assets/android-arrow-down.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/android-arrow-up.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/chrome-icon.png +0 -0
- package/v2Containers/WebPush/Create/preview/assets/edge-icon.png +0 -0
- package/v2Containers/WebPush/Create/preview/assets/firefox-icon.svg +106 -0
- package/v2Containers/WebPush/Create/preview/assets/iOS.svg +26 -0
- package/v2Containers/WebPush/Create/preview/assets/macos-arrow-down-icon.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/macos-triple-dot-icon.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +18 -0
- package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +29 -0
- package/v2Containers/WebPush/Create/preview/assets/windows-close-icon.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/windows-triple-dot-icon.svg +9 -0
- package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +51 -0
- package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +145 -0
- package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +45 -0
- package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +68 -0
- package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +61 -0
- package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +99 -0
- package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +733 -0
- package/v2Containers/WebPush/Create/preview/components/tests/WindowsChromeExpanded.test.js +571 -0
- package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +85 -0
- package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/WindowsChromeExpanded.test.js.snap +81 -0
- package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +50 -0
- package/v2Containers/WebPush/Create/preview/constants.js +637 -0
- package/v2Containers/WebPush/Create/preview/notification-container.scss +79 -0
- package/v2Containers/WebPush/Create/preview/preview.scss +358 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +370 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +12 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +12 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +12 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +47 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +11 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +11 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +11 -0
- package/v2Containers/WebPush/Create/preview/styles/_base.scss +207 -0
- package/v2Containers/WebPush/Create/preview/styles/_ios.scss +153 -0
- package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +107 -0
- package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +101 -0
- package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +229 -0
- package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +906 -0
- package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +1081 -0
- package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +723 -0
- package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +1327 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +131 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +112 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +144 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +129 -0
- package/v2Containers/WebPush/Create/utils/payloadBuilder.js +96 -0
- package/v2Containers/WebPush/Create/utils/payloadBuilder.test.js +396 -0
- package/v2Containers/WebPush/Create/utils/previewUtils.js +89 -0
- package/v2Containers/WebPush/Create/utils/urlValidation.js +115 -0
- package/v2Containers/WebPush/Create/utils/urlValidation.test.js +449 -0
- package/v2Containers/WebPush/Create/utils/validation.js +76 -0
- package/v2Containers/WebPush/Create/utils/validation.test.js +283 -0
- package/v2Containers/WebPush/actions.js +60 -0
- package/v2Containers/WebPush/constants.js +132 -0
- package/v2Containers/WebPush/index.js +2 -0
- package/v2Containers/WebPush/reducer.js +104 -0
- package/v2Containers/WebPush/sagas.js +119 -0
- package/v2Containers/WebPush/selectors.js +65 -0
- package/v2Containers/WebPush/tests/reducer.test.js +863 -0
- package/v2Containers/WebPush/tests/sagas.test.js +566 -0
- package/v2Containers/WebPush/tests/selectors.test.js +960 -0
- package/v2Containers/Whatsapp/index.js +1 -0
- package/v2Containers/Zalo/index.js +1 -0
- package/v2Containers/Zalo/tests/index.test.js +1 -5
|
@@ -17,17 +17,18 @@ import {
|
|
|
17
17
|
CapImage,
|
|
18
18
|
CapError,
|
|
19
19
|
} from '@capillarytech/cap-ui-library';
|
|
20
|
-
import { injectIntl, FormattedMessage, intlShape} from 'react-intl';
|
|
20
|
+
import { injectIntl, FormattedMessage, intlShape } from 'react-intl';
|
|
21
21
|
import { isEmpty, get } from 'lodash';
|
|
22
22
|
import './index.scss';
|
|
23
23
|
import Gallery from '../../v2Containers/Assets/Gallery';
|
|
24
24
|
import { MAX_SUPPORTED_IMAGE_SIZE } from './constants';
|
|
25
25
|
import {
|
|
26
|
-
FACEBOOK, INAPP, RCS, WHATSAPP, VIBER,
|
|
26
|
+
FACEBOOK, INAPP, RCS, WHATSAPP, VIBER, WEBPUSH, WEBPUSH_BRAND_ICON
|
|
27
27
|
} from "../../v2Containers/CreativesContainer/constants";
|
|
28
28
|
|
|
29
29
|
import messages from './messages';
|
|
30
30
|
import { MOBILEPUSH } from '../CapVideoUpload/constants';
|
|
31
|
+
import { CAP_SPACE_16 } from '@capillarytech/cap-ui-library/styled/variables';
|
|
31
32
|
function CapImageUpload(props) {
|
|
32
33
|
const {
|
|
33
34
|
intl,
|
|
@@ -50,6 +51,8 @@ function CapImageUpload(props) {
|
|
|
50
51
|
channelSpecificStyle,
|
|
51
52
|
showReUploadButton = true,
|
|
52
53
|
disableAutoRestore = false, // New prop to disable automatic restoration
|
|
54
|
+
recommendedDimensions, // Array of {width, height} objects for recommended dimensions
|
|
55
|
+
disabled = false,
|
|
53
56
|
} = props;
|
|
54
57
|
const {
|
|
55
58
|
formatMessage,
|
|
@@ -72,7 +75,7 @@ function CapImageUpload(props) {
|
|
|
72
75
|
const [isImageError, updateImageErrorMessage] = useState(false);
|
|
73
76
|
const [isDrawerRequired, updateDrawerRequirement] = useState(false);
|
|
74
77
|
|
|
75
|
-
const {CapHeadingSpan} = CapHeading;
|
|
78
|
+
const { CapHeadingSpan } = CapHeading;
|
|
76
79
|
const ImageComponent = useCallback(
|
|
77
80
|
() => (
|
|
78
81
|
<>
|
|
@@ -91,7 +94,7 @@ function CapImageUpload(props) {
|
|
|
91
94
|
|
|
92
95
|
const WithLabel = LabelHOC(ImageComponent);
|
|
93
96
|
|
|
94
|
-
const uploadImages = useCallback((e, {files}) => {
|
|
97
|
+
const uploadImages = useCallback((e, { files }) => {
|
|
95
98
|
if (e) {
|
|
96
99
|
e.preventDefault();
|
|
97
100
|
}
|
|
@@ -109,13 +112,13 @@ function CapImageUpload(props) {
|
|
|
109
112
|
height: img.height,
|
|
110
113
|
error: file && (file.size / (1e+6) > 5), // Checking if file exists and its size is greater than 5MB (5 * 10^6 bytes)
|
|
111
114
|
};
|
|
112
|
-
submitAction({file, type: 'image', fileParams}, incorrectFile);
|
|
115
|
+
submitAction({ file, type: 'image', fileParams }, incorrectFile);
|
|
113
116
|
};
|
|
114
117
|
img.onerror = () => {
|
|
115
118
|
const fileParams = {
|
|
116
119
|
error: true,
|
|
117
120
|
};
|
|
118
|
-
submitAction({fileParams}, incorrectFile);
|
|
121
|
+
submitAction({ fileParams }, incorrectFile);
|
|
119
122
|
};
|
|
120
123
|
if (e) {
|
|
121
124
|
const event = e;
|
|
@@ -123,7 +126,7 @@ function CapImageUpload(props) {
|
|
|
123
126
|
}
|
|
124
127
|
}, []);
|
|
125
128
|
|
|
126
|
-
const rcsValidation = useCallback((incorrectFile, data, size, height, width, error) => {
|
|
129
|
+
const rcsValidation = useCallback((incorrectFile, data, size, height, width, error) => {
|
|
127
130
|
if (incorrectFile || size < minImgSize || size > imgSize || height !== imgHeight || width !== imgWidth || error) {
|
|
128
131
|
updateImageErrorMessage(formatMessage(messages.imageErrorDesc));
|
|
129
132
|
} else {
|
|
@@ -146,6 +149,19 @@ function CapImageUpload(props) {
|
|
|
146
149
|
const { height, width, error } = fileParams || {};
|
|
147
150
|
if (channel === RCS) {
|
|
148
151
|
rcsValidation(incorrectFile, data, size, height, width, error);
|
|
152
|
+
} else if ([WEBPUSH, WEBPUSH_BRAND_ICON].includes(channel)) {
|
|
153
|
+
// For WEBPUSH, only validate file extension, size, and format - no dimension validation
|
|
154
|
+
if (incorrectFile || size > imgSize || error) {
|
|
155
|
+
updateImageErrorMessage(formatMessage(messages.imageErrorDesc));
|
|
156
|
+
} else {
|
|
157
|
+
updateImageErrorMessage('');
|
|
158
|
+
uploadAsset(
|
|
159
|
+
data.file,
|
|
160
|
+
data.type,
|
|
161
|
+
data.fileParams,
|
|
162
|
+
index,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
149
165
|
} else if (incorrectFile || size > imgSize || height > imgHeight || width > imgWidth || error) {
|
|
150
166
|
updateImageErrorMessage(formatMessage(messages.imageErrorDesc));
|
|
151
167
|
} else {
|
|
@@ -160,7 +176,7 @@ function CapImageUpload(props) {
|
|
|
160
176
|
}, [isImageError]);
|
|
161
177
|
|
|
162
178
|
const capUploaderCustomRequest = useCallback((uploadData) => {
|
|
163
|
-
uploadImages(undefined, {files: [uploadData.file]});
|
|
179
|
+
uploadImages(undefined, { files: [uploadData.file] });
|
|
164
180
|
}, [uploadImages]);
|
|
165
181
|
|
|
166
182
|
const setDrawerVisibility = useCallback((drawervisibleFlag) => updateDrawerRequirement(drawervisibleFlag), [isDrawerRequired]);
|
|
@@ -181,19 +197,27 @@ function CapImageUpload(props) {
|
|
|
181
197
|
secure_file_path: image, width, height, file_size: size,
|
|
182
198
|
} = get(imageTemplate, 'metaInfo', {});
|
|
183
199
|
updateDrawerRequirement(false);
|
|
184
|
-
|
|
200
|
+
// For WEBPUSH, skip dimension validation - only check extension, size
|
|
201
|
+
if ([WEBPUSH, WEBPUSH_BRAND_ICON].includes(channel)) {
|
|
202
|
+
if (!allowedExtensionsRegex.test(image) || size > imgSize) {
|
|
203
|
+
updateImageErrorMessage(formatMessage(messages.imageErrorDesc));
|
|
204
|
+
} else {
|
|
205
|
+
updateImageErrorMessage('');
|
|
206
|
+
updateImageSrc(image);
|
|
207
|
+
}
|
|
208
|
+
} else if (!allowedExtensionsRegex.test(image) || height > imgHeight || width > imgWidth || size > imgSize) {
|
|
185
209
|
updateImageErrorMessage(formatMessage(messages.imageErrorDesc));
|
|
186
210
|
} else {
|
|
187
211
|
updateImageErrorMessage('');
|
|
188
212
|
updateImageSrc(image);
|
|
189
213
|
}
|
|
190
|
-
}, [isImageError, isDrawerRequired]);
|
|
214
|
+
}, [isImageError, isDrawerRequired, channel, allowedExtensionsRegex, imgSize, formatMessage]);
|
|
191
215
|
|
|
192
216
|
const getGalleryDrawerContent = useCallback(() => {
|
|
193
217
|
const locationGallery = {
|
|
194
218
|
pathname: `/assets`,
|
|
195
219
|
search: '',
|
|
196
|
-
query: !isFullMode ? {type: 'embedded', module: 'library'} : {},
|
|
220
|
+
query: !isFullMode ? { type: 'embedded', module: 'library' } : {},
|
|
197
221
|
};
|
|
198
222
|
return (
|
|
199
223
|
<>
|
|
@@ -230,6 +254,7 @@ function CapImageUpload(props) {
|
|
|
230
254
|
customRequest={capUploaderCustomRequest}
|
|
231
255
|
className="form-builder-dragger grey-background"
|
|
232
256
|
showUploadList={!isImageError}
|
|
257
|
+
disabled={disabled}
|
|
233
258
|
>
|
|
234
259
|
<CapHeading className="dragger-title" type="h7">
|
|
235
260
|
<FormattedMessage {...messages.dragAndDrop} />
|
|
@@ -237,7 +262,7 @@ function CapImageUpload(props) {
|
|
|
237
262
|
<CapHeading className="dragger-or" type="label6">
|
|
238
263
|
<FormattedMessage {...messages.or} />
|
|
239
264
|
</CapHeading>
|
|
240
|
-
<CapButton className="dragger-button upload-image" type="secondary">
|
|
265
|
+
<CapButton className="dragger-button upload-image" type="secondary" disabled={disabled} tabIndex={disabled ? -1 : undefined}>
|
|
241
266
|
<FormattedMessage {...messages.uploadComputer} />
|
|
242
267
|
</CapButton>
|
|
243
268
|
{channel !== WHATSAPP && (
|
|
@@ -245,6 +270,8 @@ function CapImageUpload(props) {
|
|
|
245
270
|
className="dragger-button gallery-select"
|
|
246
271
|
type="secondary"
|
|
247
272
|
onClick={onGalleryClick}
|
|
273
|
+
disabled={disabled}
|
|
274
|
+
tabIndex={disabled ? -1 : undefined}
|
|
248
275
|
>
|
|
249
276
|
<FormattedMessage {...messages.uploadGallery} />
|
|
250
277
|
</CapButton>
|
|
@@ -262,13 +289,15 @@ function CapImageUpload(props) {
|
|
|
262
289
|
className="dragger-button re-upload"
|
|
263
290
|
type="flat"
|
|
264
291
|
onClick={onReUpload}
|
|
265
|
-
style={channelSpecificStyle ? { marginTop:
|
|
292
|
+
style={channelSpecificStyle ? { marginTop: `-${CAP_SPACE_16}` } : {}}
|
|
293
|
+
disabled={disabled}
|
|
294
|
+
tabIndex={disabled ? -1 : undefined}
|
|
266
295
|
>
|
|
267
296
|
<FormattedMessage {...messages.imageReUpload} />
|
|
268
297
|
</CapButton>
|
|
269
298
|
);
|
|
270
299
|
}
|
|
271
|
-
}, [isImageError, imageSrc]);
|
|
300
|
+
}, [isImageError, imageSrc, disabled, showReUploadButton, channel, channelSpecificStyle, capUploaderCustomRequest, onGalleryClick, onReUpload]);
|
|
272
301
|
|
|
273
302
|
return (
|
|
274
303
|
<div style={style} className="cap-custom-image-upload">
|
|
@@ -285,6 +314,8 @@ function CapImageUpload(props) {
|
|
|
285
314
|
type="file"
|
|
286
315
|
onChange={(e) => uploadImages(e, { files: e.target.files })}
|
|
287
316
|
accept={supportedExtensions || "image/*"}
|
|
317
|
+
disabled={disabled}
|
|
318
|
+
tabIndex={disabled ? -1 : undefined}
|
|
288
319
|
/>
|
|
289
320
|
{getImageSection()}
|
|
290
321
|
<CapDrawer
|
|
@@ -305,7 +336,18 @@ function CapImageUpload(props) {
|
|
|
305
336
|
)}
|
|
306
337
|
{![WHATSAPP, INAPP].includes(channel) && (
|
|
307
338
|
<CapHeadingSpan type="label2" className="image-dimension">
|
|
308
|
-
|
|
339
|
+
{[WEBPUSH, WEBPUSH_BRAND_ICON].includes(channel) && recommendedDimensions?.length ? (
|
|
340
|
+
<FormattedMessage
|
|
341
|
+
{...messages.recommendedDimensions}
|
|
342
|
+
values={{
|
|
343
|
+
dimensions: recommendedDimensions
|
|
344
|
+
.map((dim) => `${dim.width} x ${dim.height}px`)
|
|
345
|
+
.join(', '),
|
|
346
|
+
}}
|
|
347
|
+
/>
|
|
348
|
+
) : (
|
|
349
|
+
<FormattedMessage {...messages.imageDimenstionDescription} values={{ width: imgWidth, height: imgHeight }} />
|
|
350
|
+
)}
|
|
309
351
|
</CapHeadingSpan>
|
|
310
352
|
)}
|
|
311
353
|
{channel === FACEBOOK && (
|
|
@@ -328,7 +370,7 @@ function CapImageUpload(props) {
|
|
|
328
370
|
getImageSizeLabel()
|
|
329
371
|
)
|
|
330
372
|
)}
|
|
331
|
-
{[VIBER, INAPP, MOBILEPUSH].includes(channel) && getImageSizeLabel()}
|
|
373
|
+
{[VIBER, INAPP, MOBILEPUSH, WEBPUSH, WEBPUSH_BRAND_ICON].includes(channel) && getImageSizeLabel()}
|
|
332
374
|
<CapHeadingSpan type="label2" className="image-format">
|
|
333
375
|
{channel === INAPP ? <FormattedMessage {...messages.format2} /> : <FormattedMessage {...messages.format} />}
|
|
334
376
|
</CapHeadingSpan>
|
|
@@ -357,6 +399,13 @@ CapImageUpload.propTypes = {
|
|
|
357
399
|
channel: PropTypes.string,
|
|
358
400
|
channelSpecificStyle: PropTypes.bool,
|
|
359
401
|
disableAutoRestore: PropTypes.bool,
|
|
402
|
+
recommendedDimensions: PropTypes.arrayOf(
|
|
403
|
+
PropTypes.shape({
|
|
404
|
+
width: PropTypes.number.isRequired,
|
|
405
|
+
height: PropTypes.number.isRequired,
|
|
406
|
+
})
|
|
407
|
+
),
|
|
408
|
+
disabled: PropTypes.bool,
|
|
360
409
|
};
|
|
361
410
|
|
|
362
411
|
export default injectIntl(CapImageUpload);
|
|
@@ -34,6 +34,10 @@ export default defineMessages({
|
|
|
34
34
|
id: `${scope}.imageDimenstionDescription`,
|
|
35
35
|
defaultMessage: 'Dimensions upto: {width}px x {height}px',
|
|
36
36
|
},
|
|
37
|
+
recommendedDimensions: {
|
|
38
|
+
id: `${scope}.recommendedDimensions`,
|
|
39
|
+
defaultMessage: 'Recommended dimensions: {dimensions}',
|
|
40
|
+
},
|
|
37
41
|
format: {
|
|
38
42
|
id: `${scope}.format`,
|
|
39
43
|
defaultMessage: 'Format: JPEG, JPG, PNG',
|
|
@@ -57,7 +61,7 @@ export default defineMessages({
|
|
|
57
61
|
},
|
|
58
62
|
channelImageSize: {
|
|
59
63
|
id: `${scope}.channelImageSize`,
|
|
60
|
-
defaultMessage: 'Size
|
|
64
|
+
defaultMessage: 'Size up to: {size}MB',
|
|
61
65
|
},
|
|
62
66
|
RcschannelImageSize: {
|
|
63
67
|
id: `${scope}.RcschannelImageSize`,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Default allowed content types for image URL validation
|
|
2
|
+
export const DEFAULT_ALLOWED_CONTENT_TYPES = ['image/jpeg', 'image/jpg', 'image/png'];
|
|
3
|
+
|
|
4
|
+
// Default maximum file size (5MB)
|
|
5
|
+
export const DEFAULT_MAX_SIZE = 5000000;
|
|
6
|
+
|
|
7
|
+
// Default allowed extensions regex
|
|
8
|
+
export const DEFAULT_ALLOWED_EXTENSIONS_REGEX = /\.(jpe?g|png)$/i;
|
|
9
|
+
|
|
10
|
+
// MIME type to file extension mapping
|
|
11
|
+
export const MIME_TYPE_TO_EXTENSION = {
|
|
12
|
+
'image/jpeg': 'jpg',
|
|
13
|
+
'image/jpg': 'jpg',
|
|
14
|
+
'image/png': 'png',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Default image extension fallback
|
|
18
|
+
export const DEFAULT_IMAGE_EXTENSION = 'png';
|
|
19
|
+
|
|
20
|
+
// Upload status state machine states
|
|
21
|
+
export const UPLOAD_STATUS = {
|
|
22
|
+
IDLE: 'idle',
|
|
23
|
+
UPLOADING: 'uploading',
|
|
24
|
+
WAITING: 'waiting',
|
|
25
|
+
};
|
|
26
|
+
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* CapImageUrlUpload
|
|
4
|
+
*
|
|
5
|
+
* A modular component for uploading images from a URL.
|
|
6
|
+
* Validates URL format, image type, and size before uploading to gallery.
|
|
7
|
+
* Can be used in any form context (creatives, profiles, etc.)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React, { useState, useCallback, useEffect } from 'react';
|
|
11
|
+
import PropTypes from 'prop-types';
|
|
12
|
+
import { injectIntl, intlShape, FormattedMessage } from 'react-intl';
|
|
13
|
+
import CapInput from '@capillarytech/cap-ui-library/CapInput';
|
|
14
|
+
import CapHeading from '@capillarytech/cap-ui-library/CapHeading';
|
|
15
|
+
import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
|
|
16
|
+
import CapError from '@capillarytech/cap-ui-library/CapError';
|
|
17
|
+
import messages from './messages';
|
|
18
|
+
import { DEFAULT_ALLOWED_CONTENT_TYPES, DEFAULT_MAX_SIZE, UPLOAD_STATUS } from './constants';
|
|
19
|
+
import { fetchImageFromUrl, uploadImageFromUrlHelper } from '../../utils/imageUrlUpload';
|
|
20
|
+
import './index.scss';
|
|
21
|
+
|
|
22
|
+
function CapImageUrlUpload(props) {
|
|
23
|
+
const {
|
|
24
|
+
intl,
|
|
25
|
+
uploadAsset,
|
|
26
|
+
imgSize = DEFAULT_MAX_SIZE,
|
|
27
|
+
allowedContentTypes = DEFAULT_ALLOWED_CONTENT_TYPES,
|
|
28
|
+
recommendedDimensions = [],
|
|
29
|
+
sizeLabel = '',
|
|
30
|
+
formatLabel = '',
|
|
31
|
+
imageUrl = '',
|
|
32
|
+
imageSrc = '', // Secure file path from parent after upload completes
|
|
33
|
+
onUrlChange,
|
|
34
|
+
onValidationStateChange, // Callback to notify parent of validation state
|
|
35
|
+
onUploadStateChange, // Callback to notify parent of upload state
|
|
36
|
+
isExternalUploading = false, // Upload state from parent (e.g., redux)
|
|
37
|
+
className = '',
|
|
38
|
+
placeholder,
|
|
39
|
+
disabled = false,
|
|
40
|
+
fileNamePrefix = 'image',
|
|
41
|
+
} = props;
|
|
42
|
+
|
|
43
|
+
const { formatMessage } = intl ?? {};
|
|
44
|
+
const { CapHeadingSpan } = CapHeading;
|
|
45
|
+
|
|
46
|
+
const [isValidating, setIsValidating] = useState(false);
|
|
47
|
+
const [isInternalUploading, setIsInternalUploading] = useState(false);
|
|
48
|
+
const [error, setError] = useState('');
|
|
49
|
+
// State machine for upload lifecycle
|
|
50
|
+
const [uploadStatus, setUploadStatus] = useState(UPLOAD_STATUS.IDLE);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Validate image URL
|
|
54
|
+
* Checks URL format, fetches as Blob, validates Content-Type, file size, and image validity
|
|
55
|
+
*/
|
|
56
|
+
const validateImageUrl = useCallback(async (url) => {
|
|
57
|
+
const trimmedUrl = url?.trim() || '';
|
|
58
|
+
|
|
59
|
+
if (!trimmedUrl) {
|
|
60
|
+
return { isValid: false, error: '' };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
setIsValidating(true);
|
|
64
|
+
setError('');
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// Validate URL format
|
|
68
|
+
let urlObj;
|
|
69
|
+
try {
|
|
70
|
+
urlObj = new URL(trimmedUrl);
|
|
71
|
+
if (!['http:', 'https:'].includes(urlObj.protocol)) {
|
|
72
|
+
setIsValidating(false);
|
|
73
|
+
return {
|
|
74
|
+
isValid: false,
|
|
75
|
+
error: formatMessage(messages.imageUrlInvalid),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
} catch (urlError) {
|
|
79
|
+
setIsValidating(false);
|
|
80
|
+
return {
|
|
81
|
+
isValid: false,
|
|
82
|
+
error: formatMessage(messages.imageUrlInvalid),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Fetch image as Blob to check file size
|
|
87
|
+
let response;
|
|
88
|
+
try {
|
|
89
|
+
response = await fetchImageFromUrl(trimmedUrl);
|
|
90
|
+
} catch (fetchError) {
|
|
91
|
+
setIsValidating(false);
|
|
92
|
+
return {
|
|
93
|
+
isValid: false,
|
|
94
|
+
error: formatMessage(messages.imageLoadError),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Validate Content-Type
|
|
99
|
+
const contentType = response.headers?.get('Content-Type') || '';
|
|
100
|
+
const normalizedContentType = contentType.split(';')[0].toLowerCase().trim();
|
|
101
|
+
|
|
102
|
+
if (!allowedContentTypes.includes(normalizedContentType)) {
|
|
103
|
+
setIsValidating(false);
|
|
104
|
+
return {
|
|
105
|
+
isValid: false,
|
|
106
|
+
error: formatMessage(messages.imageTypeInvalid),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Validate file size
|
|
111
|
+
const blob = await response.blob();
|
|
112
|
+
if (blob.size > imgSize) {
|
|
113
|
+
setIsValidating(false);
|
|
114
|
+
return {
|
|
115
|
+
isValid: false,
|
|
116
|
+
error: formatMessage(messages.imageSizeInvalid),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Verify image validity by loading in Image object
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
const img = new Image();
|
|
123
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
124
|
+
|
|
125
|
+
img.onload = () => {
|
|
126
|
+
URL.revokeObjectURL(objectUrl);
|
|
127
|
+
setIsValidating(false);
|
|
128
|
+
resolve({
|
|
129
|
+
isValid: true,
|
|
130
|
+
error: '',
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
img.onerror = () => {
|
|
135
|
+
URL.revokeObjectURL(objectUrl);
|
|
136
|
+
setIsValidating(false);
|
|
137
|
+
resolve({
|
|
138
|
+
isValid: false,
|
|
139
|
+
error: formatMessage(messages.imageLoadError),
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
img.src = objectUrl;
|
|
144
|
+
});
|
|
145
|
+
} catch (error) {
|
|
146
|
+
setIsValidating(false);
|
|
147
|
+
return {
|
|
148
|
+
isValid: false,
|
|
149
|
+
error: formatMessage(messages.imageLoadError),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}, [formatMessage, allowedContentTypes, imgSize]);
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Upload image from URL to media gallery
|
|
156
|
+
* Fetches image, converts to File, uploads via uploadAsset
|
|
157
|
+
*/
|
|
158
|
+
const uploadImageFromUrl = useCallback(async (url) => {
|
|
159
|
+
setIsInternalUploading(true);
|
|
160
|
+
setUploadStatus(UPLOAD_STATUS.UPLOADING);
|
|
161
|
+
|
|
162
|
+
// Notify parent that upload is starting
|
|
163
|
+
if (onUploadStateChange) {
|
|
164
|
+
onUploadStateChange(true);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const result = await uploadImageFromUrlHelper(
|
|
168
|
+
url,
|
|
169
|
+
formatMessage,
|
|
170
|
+
messages,
|
|
171
|
+
uploadAsset,
|
|
172
|
+
fileNamePrefix,
|
|
173
|
+
imgSize,
|
|
174
|
+
allowedContentTypes,
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
setIsInternalUploading(false);
|
|
178
|
+
|
|
179
|
+
// Check if imageSrc is already available (handles React batching/timing issues)
|
|
180
|
+
if (result.success && imageSrc && imageSrc !== '') {
|
|
181
|
+
// Upload already complete - imageSrc was set before we entered waiting state
|
|
182
|
+
setUploadStatus(UPLOAD_STATUS.IDLE);
|
|
183
|
+
if (onUploadStateChange) {
|
|
184
|
+
onUploadStateChange(false);
|
|
185
|
+
}
|
|
186
|
+
} else if (result.success) {
|
|
187
|
+
// Transition to waiting state - we've triggered the upload, now wait for imageSrc
|
|
188
|
+
setUploadStatus(UPLOAD_STATUS.WAITING);
|
|
189
|
+
} else {
|
|
190
|
+
// Upload failed
|
|
191
|
+
setUploadStatus(UPLOAD_STATUS.IDLE);
|
|
192
|
+
if (onUploadStateChange) {
|
|
193
|
+
onUploadStateChange(false);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return result;
|
|
198
|
+
}, [formatMessage, uploadAsset, fileNamePrefix, imgSize, allowedContentTypes, onUploadStateChange, imageSrc]);
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Handle image URL change
|
|
202
|
+
* Validates URL and triggers upload on success
|
|
203
|
+
*/
|
|
204
|
+
const handleImageUrlChange = useCallback(async (e) => {
|
|
205
|
+
const url = e.target.value;
|
|
206
|
+
const trimmedUrl = url?.trim() || '';
|
|
207
|
+
|
|
208
|
+
// Reset upload status when URL changes
|
|
209
|
+
setUploadStatus(UPLOAD_STATUS.IDLE);
|
|
210
|
+
|
|
211
|
+
// Call parent's onUrlChange if provided
|
|
212
|
+
if (onUrlChange) {
|
|
213
|
+
onUrlChange(url);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (trimmedUrl !== '') {
|
|
217
|
+
// Notify parent that validation is starting
|
|
218
|
+
if (onValidationStateChange) {
|
|
219
|
+
onValidationStateChange(true);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const validation = await validateImageUrl(trimmedUrl);
|
|
223
|
+
|
|
224
|
+
// Notify parent that validation is complete
|
|
225
|
+
if (onValidationStateChange) {
|
|
226
|
+
onValidationStateChange(false);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!validation.isValid) {
|
|
230
|
+
setError(validation.error);
|
|
231
|
+
if (onUploadStateChange) {
|
|
232
|
+
onUploadStateChange(false);
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
setError('');
|
|
236
|
+
// Upload image from URL when validation succeeds
|
|
237
|
+
const uploadResult = await uploadImageFromUrl(trimmedUrl);
|
|
238
|
+
if (!uploadResult.success) {
|
|
239
|
+
setError(uploadResult.error);
|
|
240
|
+
if (onUploadStateChange) {
|
|
241
|
+
onUploadStateChange(false);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
setError('');
|
|
247
|
+
if (onValidationStateChange) {
|
|
248
|
+
onValidationStateChange(false);
|
|
249
|
+
}
|
|
250
|
+
if (onUploadStateChange) {
|
|
251
|
+
onUploadStateChange(false);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}, [validateImageUrl, uploadImageFromUrl, onUrlChange, onValidationStateChange, onUploadStateChange]);
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Monitor upload status and imageSrc to detect when upload is complete
|
|
258
|
+
* Handles React batching and timing issues by checking state transitions
|
|
259
|
+
*/
|
|
260
|
+
useEffect(() => {
|
|
261
|
+
if ((uploadStatus === UPLOAD_STATUS.UPLOADING || uploadStatus === UPLOAD_STATUS.WAITING) && imageSrc && imageSrc !== '') {
|
|
262
|
+
// Upload is complete - we have the secure file path
|
|
263
|
+
setUploadStatus(UPLOAD_STATUS.IDLE);
|
|
264
|
+
|
|
265
|
+
// Notify parent that upload is complete
|
|
266
|
+
if (onUploadStateChange) {
|
|
267
|
+
onUploadStateChange(false);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}, [uploadStatus, imageSrc, onUploadStateChange]);
|
|
271
|
+
|
|
272
|
+
// Determine if we should show "uploading" state
|
|
273
|
+
// Show uploading when:
|
|
274
|
+
// 1. Internal upload is in progress, OR
|
|
275
|
+
// 2. External upload is in progress (from parent's redux), OR
|
|
276
|
+
// 3. State machine indicates we're uploading or waiting for completion
|
|
277
|
+
const isActuallyUploading = isInternalUploading || isExternalUploading || uploadStatus !== UPLOAD_STATUS.IDLE;
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<div className={`cap-image-url-upload ${className}`}>
|
|
281
|
+
<CapInput
|
|
282
|
+
className="image-url-input"
|
|
283
|
+
placeholder={placeholder || formatMessage(messages.imageUrlPlaceholder)}
|
|
284
|
+
value={imageUrl}
|
|
285
|
+
onChange={handleImageUrlChange}
|
|
286
|
+
size="default"
|
|
287
|
+
errorMessage={
|
|
288
|
+
error && (
|
|
289
|
+
<CapError className="image-url-error">
|
|
290
|
+
{error}
|
|
291
|
+
</CapError>
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
disabled={disabled || isValidating || isActuallyUploading}
|
|
295
|
+
/>
|
|
296
|
+
|
|
297
|
+
{isValidating && (
|
|
298
|
+
<CapLabel type="label2" className="validation-label">
|
|
299
|
+
<FormattedMessage {...messages.validatingUrl} />
|
|
300
|
+
</CapLabel>
|
|
301
|
+
)}
|
|
302
|
+
|
|
303
|
+
{isActuallyUploading && !isValidating && (
|
|
304
|
+
<CapLabel type="label2" className="uploading-label">
|
|
305
|
+
<FormattedMessage {...messages.uploadingImage} />
|
|
306
|
+
</CapLabel>
|
|
307
|
+
)}
|
|
308
|
+
|
|
309
|
+
<div className="webpush-image-specs">
|
|
310
|
+
{recommendedDimensions?.length > 0 && (
|
|
311
|
+
<CapHeadingSpan type="label2" className="image-dimension">
|
|
312
|
+
<FormattedMessage
|
|
313
|
+
{...messages.recommendedDimensions}
|
|
314
|
+
values={{
|
|
315
|
+
dimensions: recommendedDimensions
|
|
316
|
+
.map((dim) => `${dim.width} x ${dim.height}px`)
|
|
317
|
+
.join(', '),
|
|
318
|
+
}}
|
|
319
|
+
/>
|
|
320
|
+
</CapHeadingSpan>
|
|
321
|
+
)}
|
|
322
|
+
|
|
323
|
+
{sizeLabel && (
|
|
324
|
+
<CapHeadingSpan type="label2" className="image-size">
|
|
325
|
+
{sizeLabel}
|
|
326
|
+
</CapHeadingSpan>
|
|
327
|
+
)}
|
|
328
|
+
|
|
329
|
+
{formatLabel && (
|
|
330
|
+
<CapHeadingSpan type="label2" className="image-format">
|
|
331
|
+
{formatLabel}
|
|
332
|
+
</CapHeadingSpan>
|
|
333
|
+
)}
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
CapImageUrlUpload.propTypes = {
|
|
340
|
+
intl: intlShape.isRequired,
|
|
341
|
+
uploadAsset: PropTypes.func.isRequired,
|
|
342
|
+
imgSize: PropTypes.number,
|
|
343
|
+
allowedContentTypes: PropTypes.arrayOf(PropTypes.string),
|
|
344
|
+
recommendedDimensions: PropTypes.arrayOf(
|
|
345
|
+
PropTypes.shape({
|
|
346
|
+
width: PropTypes.number.isRequired,
|
|
347
|
+
height: PropTypes.number.isRequired,
|
|
348
|
+
})
|
|
349
|
+
),
|
|
350
|
+
sizeLabel: PropTypes.string,
|
|
351
|
+
formatLabel: PropTypes.string,
|
|
352
|
+
imageUrl: PropTypes.string,
|
|
353
|
+
imageSrc: PropTypes.string, // Secure file path from parent after upload completes
|
|
354
|
+
onUrlChange: PropTypes.func,
|
|
355
|
+
onValidationStateChange: PropTypes.func, // Callback(isValidating: bool)
|
|
356
|
+
onUploadStateChange: PropTypes.func, // Callback(isUploading: bool)
|
|
357
|
+
isExternalUploading: PropTypes.bool, // Upload state from parent (e.g., redux)
|
|
358
|
+
className: PropTypes.string,
|
|
359
|
+
placeholder: PropTypes.string,
|
|
360
|
+
disabled: PropTypes.bool,
|
|
361
|
+
fileNamePrefix: PropTypes.string,
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
export default injectIntl(CapImageUrlUpload);
|
|
365
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
@import '~@capillarytech/cap-ui-library/styles/_variables.scss';
|
|
2
|
+
|
|
3
|
+
@mixin cap-image-url-spec-text {
|
|
4
|
+
.image-dimension,
|
|
5
|
+
.image-size,
|
|
6
|
+
.image-format {
|
|
7
|
+
color: $CAP_G01;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.cap-image-url-upload {
|
|
12
|
+
.image-url-input {
|
|
13
|
+
width: 100%;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.validation-label,
|
|
17
|
+
.uploading-label {
|
|
18
|
+
margin-top: $CAP_SPACE_08;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.image-url-error {
|
|
22
|
+
margin-top: $CAP_SPACE_04;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.webpush-image-specs {
|
|
26
|
+
@include cap-image-url-spec-text;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.webpush-container & {
|
|
30
|
+
.webpush-image-specs {
|
|
31
|
+
@include cap-image-url-spec-text;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|