-
{{*operationTooltip}}
+
diff --git a/blocks/CloudImageEditor/src/css/common.css b/blocks/CloudImageEditor/src/css/common.css
index ae0eaca27..331cb23da 100644
--- a/blocks/CloudImageEditor/src/css/common.css
+++ b/blocks/CloudImageEditor/src/css/common.css
@@ -377,6 +377,14 @@ lr-editor-filter-control.active {
--idle-color-rgb: var(--rgb-primary-accent);
}
+lr-editor-filter-control.not_active .preview[loaded] {
+ opacity: 1;
+}
+
+lr-editor-filter-control.active .preview {
+ opacity: 0;
+}
+
lr-editor-button-control.not_active,
lr-editor-operation-control.not_active,
lr-editor-crop-button-control.not_active,
@@ -687,7 +695,7 @@ lr-editor-toolbar .controls-list_last-item {
margin-right: var(--cldtr-gap-max);
}
-lr-editor-toolbar .filter-tooltip_container {
+lr-editor-toolbar .info-tooltip_container {
position: absolute;
display: flex;
align-items: flex-start;
@@ -696,7 +704,7 @@ lr-editor-toolbar .filter-tooltip_container {
height: 100%;
}
-lr-editor-toolbar .filter-tooltip_wrapper {
+lr-editor-toolbar .info-tooltip_wrapper {
position: absolute;
top: calc(-100% - var(--cldtr-gap-mid-2));
display: flex;
@@ -706,7 +714,7 @@ lr-editor-toolbar .filter-tooltip_wrapper {
pointer-events: none;
}
-lr-editor-toolbar .filter-tooltip {
+lr-editor-toolbar .info-tooltip {
z-index: 3;
padding-top: calc(var(--cldtr-gap-min) / 2);
padding-right: var(--cldtr-gap-min);
@@ -718,20 +726,16 @@ lr-editor-toolbar .filter-tooltip {
text-transform: uppercase;
background-color: var(--color-text-accent-contrast);
border-radius: var(--border-radius-editor);
+ transform: translateY(100%);
opacity: 0;
transition: var(--transition-duration-3);
}
-lr-editor-toolbar .filter-tooltip_visible {
+lr-editor-toolbar .info-tooltip_visible {
transform: translateY(0px);
opacity: 1;
}
-lr-editor-toolbar .filter-tooltip_hidden {
- transform: translateY(100%);
- opacity: 0;
-}
-
lr-editor-toolbar .slider {
padding-right: var(--l-slider-padding);
padding-left: var(--l-slider-padding);
diff --git a/blocks/CloudImageEditor/src/lib/transformationUtils.js b/blocks/CloudImageEditor/src/lib/transformationUtils.js
index 44ea60b73..862498ea1 100644
--- a/blocks/CloudImageEditor/src/lib/transformationUtils.js
+++ b/blocks/CloudImageEditor/src/lib/transformationUtils.js
@@ -46,7 +46,8 @@ function transformationToStr(operation, options) {
return '';
}
-const ORDER = [
+// TODO: refactor all the operations constants
+const SUPPORTED_OPERATIONS_ORDERED = [
'enhance',
'brightness',
'exposure',
@@ -68,7 +69,7 @@ const ORDER = [
*/
export function transformationsToOperations(transformations) {
return joinCdnOperations(
- ...ORDER.filter(
+ ...SUPPORTED_OPERATIONS_ORDERED.filter(
(operation) => typeof transformations[operation] !== 'undefined' && transformations[operation] !== null
)
.map((operation) => {
@@ -80,3 +81,53 @@ export function transformationsToOperations(transformations) {
}
export const COMMON_OPERATIONS = joinCdnOperations('format/auto', 'progressive/yes');
+
+const asNumber = ([value]) => (typeof value !== 'undefined' ? Number(value) : undefined);
+const asBoolean = () => true;
+const asFilter = ([name, amount]) => ({
+ name,
+ amount: Number(amount),
+});
+
+// Docs: https://uploadcare.com/docs/transformations/image/resize-crop/#operation-crop
+// We don't support percentages and aligment presets,
+// Because it's unclear how to handle them in the Editor UI
+// TODO: add support for percentages and aligment presets
+const asCrop = ([dimensions, coords]) => {
+ return { dimensions: dimensions.split('x').map(Number), coords: coords.split(',').map(Number) };
+};
+
+const OPERATION_PROCESSORS = {
+ enhance: asNumber,
+ brightness: asNumber,
+ exposure: asNumber,
+ gamma: asNumber,
+ contrast: asNumber,
+ saturation: asNumber,
+ vibrance: asNumber,
+ warmth: asNumber,
+ filter: asFilter,
+ mirror: asBoolean,
+ flip: asBoolean,
+ rotate: asNumber,
+ crop: asCrop,
+};
+
+/**
+ * @param {string[]} operations
+ * @returns {import('../types.js').Transformations}
+ */
+export function operationsToTransformations(operations) {
+ /** @type {import('../types.js').Transformations} */
+ let transformations = {};
+ for (let operation of operations) {
+ let [name, ...args] = operation.split('/');
+ if (!SUPPORTED_OPERATIONS_ORDERED.includes(name)) {
+ continue;
+ }
+ const processor = OPERATION_PROCESSORS[name];
+ const value = processor(args);
+ transformations[name] = value;
+ }
+ return transformations;
+}
diff --git a/blocks/CloudImageEditor/src/state.js b/blocks/CloudImageEditor/src/state.js
index 4a4bd85ca..c1a61c4c1 100644
--- a/blocks/CloudImageEditor/src/state.js
+++ b/blocks/CloudImageEditor/src/state.js
@@ -6,13 +6,14 @@ import { TRANSPARENT_PIXEL_SRC } from '../../../utils/transparentPixelSrc.js';
export function initState(fnCtx) {
return {
'*originalUrl': null,
- '*tabId': null,
'*faderEl': null,
'*cropperEl': null,
'*imgEl': null,
'*imgContainerEl': null,
'*networkProblems': false,
'*imageSize': null,
+ /** @type {import('./types.js').Transformations} */
+ '*editorTransformations': {},
entry: null,
extension: null,
@@ -24,6 +25,7 @@ export function initState(fnCtx) {
fileType: '',
showLoader: false,
uuid: null,
+ cdnUrl: null,
'presence.networkProblems': false,
'presence.modalCaption': true,
diff --git a/utils/cdn-utils.js b/utils/cdn-utils.js
index 7ba796816..c234adc6b 100644
--- a/utils/cdn-utils.js
+++ b/utils/cdn-utils.js
@@ -68,6 +68,41 @@ export function extractFilename(cdnUrl) {
return filename;
}
+/**
+ * Extract UUID from CDN URL
+ *
+ * @param {string} cdnUrl
+ * @returns {string}
+ */
+export function extractUuid(cdnUrl) {
+ let url = new URL(cdnUrl);
+ let { pathname } = url;
+ const slashIndex = pathname.indexOf('/');
+ const secondSlashIndex = pathname.indexOf('/', slashIndex + 1);
+ return pathname.substring(slashIndex + 1, secondSlashIndex);
+}
+
+/**
+ * Extract UUID from CDN URL
+ *
+ * @param {string} cdnUrl
+ * @returns {string[]}
+ */
+export function extractOperations(cdnUrl) {
+ let withoutFilename = trimFilename(cdnUrl);
+ let url = new URL(withoutFilename);
+ let operationsMarker = url.pathname.indexOf('/-/');
+ if (operationsMarker === -1) {
+ return [];
+ }
+ let operationsStr = url.pathname.substring(operationsMarker);
+
+ return operationsStr
+ .split('/-/')
+ .filter(Boolean)
+ .map((operation) => normalizeCdnOperation(operation));
+}
+
/**
* Trim filename or file URL
*
diff --git a/utils/cdn-utils.test.js b/utils/cdn-utils.test.js
index 23ec899d2..11583cee9 100644
--- a/utils/cdn-utils.test.js
+++ b/utils/cdn-utils.test.js
@@ -7,6 +7,8 @@ import {
createOriginalUrl,
extractFilename,
trimFilename,
+ extractUuid,
+ extractOperations,
} from './cdn-utils.js';
const falsyValues = ['', undefined, null, false, true, 0, 10];
@@ -183,3 +185,29 @@ describe('cdn-utils/trimFilename', () => {
);
});
});
+
+describe('cdn-utils/extractUuid', () => {
+ it('should extract uuid from cdn url', () => {
+ expect(extractUuid('https://ucarecdn.com/:uuid/image.jpeg')).to.eq(':uuid');
+ expect(extractUuid('https://ucarecdn.com/:uuid/-/resize/100x/image.jpeg')).to.eq(':uuid');
+
+ expect(extractUuid('https://ucarecdn.com/c2499162-eb07-4b93-b31e-94a89a47e858/image.jpeg')).to.eq(
+ 'c2499162-eb07-4b93-b31e-94a89a47e858'
+ );
+ expect(extractUuid('https://ucarecdn.com/c2499162-eb07-4b93-b31e-94a89a47e858/-/resize/100x/image.jpeg')).to.eq(
+ 'c2499162-eb07-4b93-b31e-94a89a47e858'
+ );
+ });
+});
+
+describe('cdn-utils/extractOperations', () => {
+ it('should extract operations from cdn url', () => {
+ expect(extractOperations('https://ucarecdn.com/:uuid/image.jpeg')).to.eql([]);
+ expect(
+ extractOperations('https://ucarecdn.com/c2499162-eb07-4b93-b31e-94a89a47e858/-/resize/100x/image.jpeg')
+ ).to.eql(['resize/100x']);
+ expect(extractOperations('https://domain.ucr.io:8080/-/resize/100x/https://domain.com/image.jpg?q=1#hash')).to.eql([
+ 'resize/100x',
+ ]);
+ });
+});