Skip to content

Commit

Permalink
fix(image-editor): restore transformations state from the cdn url
Browse files Browse the repository at this point in the history
  • Loading branch information
nd0ut committed Jan 12, 2023
1 parent d18f3a6 commit 32b1858
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 128 deletions.
12 changes: 6 additions & 6 deletions blocks/CloudImageEditor/CloudImageEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export class CloudImageEditor extends UploaderBlock {

init$ = {
...this.ctxInit,
uuid: null,
cdnUrl: null,
};

initCallback() {
Expand All @@ -25,9 +25,9 @@ export class CloudImageEditor extends UploaderBlock {
}
this.entry = entry;

this.entry.subscribe('uuid', (uuid) => {
if (uuid) {
this.$.uuid = uuid;
this.entry.subscribe('cdnUrl', (cdnUrl) => {
if (cdnUrl) {
this.$.cdnUrl = cdnUrl;
}
});
});
Expand All @@ -50,8 +50,8 @@ export class CloudImageEditor extends UploaderBlock {
mountEditor() {
let instance = new CloudEditor();
instance.classList.add('lr-cldtr-common');
let uuid = this.$.uuid;
instance.setAttribute('uuid', uuid);
let cdnUrl = this.$.cdnUrl;
instance.setAttribute('cdn-url', cdnUrl);

instance.addEventListener('apply', (result) => this.handleApply(result));
instance.addEventListener('cancel', () => this.handleCancel());
Expand Down
14 changes: 13 additions & 1 deletion blocks/CloudImageEditor/ref.htm
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
<h1>Cloud image editor</h1>

<h2>Load image by UUID</h2>

<lr-live-html>
<link rel="stylesheet" href="./src/css/index.css" />

<script src="./test.js" type="module"></script>

<lr-cloud-editor class="lr-cldtr-common" uuid="7c167b79-9f27-4489-8032-3f3be1840605"></lr-cloud-editor>
</lr-live-html>
</lr-live-html>

<h2>Load image by CDN URL</h2>

<lr-live-html>
<link rel="stylesheet" href="./src/css/index.css" />

<script src="./test.js" type="module"></script>

<lr-cloud-editor class="lr-cldtr-common" cdn-url="https://ucarecdn.com/7c167b79-9f27-4489-8032-3f3be1840605/-/crop/100x100/0,0/"></lr-cloud-editor>
</lr-live-html>
85 changes: 21 additions & 64 deletions blocks/CloudImageEditor/src/CloudEditor.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { Block } from '../../../abstract/Block.js';
import { createCdnUrl, createCdnUrlModifiers, createOriginalUrl } from '../../../utils/cdn-utils.js';
import {
createCdnUrl,
createCdnUrlModifiers,
createOriginalUrl,
extractOperations,
extractUuid,
} from '../../../utils/cdn-utils.js';
import { TRANSPARENT_PIXEL_SRC } from '../../../utils/transparentPixelSrc.js';
import { classNames } from './lib/classNames.js';
import { debounce } from './lib/debounce.js';
import { preloadImage } from './lib/preloadImage.js';
import { TRANSPARENT_PIXEL_SRC } from '../../../utils/transparentPixelSrc.js';
import { operationsToTransformations } from './lib/transformationUtils.js';
import { initState } from './state.js';
import { TEMPLATE } from './template.js';
import { TabId } from './toolbar-constants.js';
import { viewerImageSrc } from './util.js';

export class CloudEditor extends Block {
get ctxName() {
Expand All @@ -26,69 +31,22 @@ export class CloudEditor extends Block {
this.$.showLoader = show;
}

_loadImageFromCdn() {
this._debouncedShowLoader(true);
let src = this._imageSrc();
let { promise, cancel } = preloadImage(src);
promise
.then(() => {
this.$.src = src;
})
.catch((err) => {
this.$['*networkProblems'] = true;
this._debouncedShowLoader(false);
this.$.src = src;
});
this._cancelPreload && this._cancelPreload();
this._cancelPreload = cancel;
}

_imageSrc() {
let { width } = this.ref['img-container-el'].getBoundingClientRect();
return this.proxyUrl(viewerImageSrc(this.$['*originalUrl'], width, {}));
}

/**
* To proper work, we need non-zero size the element. So, we'll wait for it.
*
* @private
* @returns {Promise<void>}
*/
_waitForSize() {
return new Promise((resolve, reject) => {
let timeout = 300;
let start = Date.now();

let callback = () => {
// there could be problem when element disconnected and connected again between ticks
if (!this.isConnected) {
clearInterval(interval);
reject();
return;
}
if (Date.now() - start > timeout) {
clearInterval(interval);
reject(new Error('[cloud-image-editor] timout waiting for non-zero container size'));
return;
}
let { width, height } = this.getBoundingClientRect();

if (width > 0 && height > 0) {
clearInterval(interval);
resolve();
}
};
let interval = setInterval(callback, 50);
callback();
});
}

cssInit$ = {
'--cfg-cdn-cname': 'https://ucarecdn.com',
};

async initCallback() {
this.$['*originalUrl'] = createOriginalUrl(this.localCtx.read('--cfg-cdn-cname'), this.$.uuid);
if (this.$.cdnUrl) {
let uuid = extractUuid(this.$.cdnUrl);
this.$['*originalUrl'] = createOriginalUrl(this.$.cdnUrl, uuid);
let operations = extractOperations(this.$.cdnUrl);
let transformations = operationsToTransformations(operations);
this.$['*editorTransformations'] = transformations;
} else if (this.$.uuid) {
this.$['*originalUrl'] = createOriginalUrl(this.localCtx.read('--cfg-cdn-cname'), this.$.uuid);
} else {
throw new Error('No UUID nor CDN URL provided');
}

this.$['*faderEl'] = this.ref['fader-el'];
this.$['*cropperEl'] = this.ref['cropper-el'];
Expand Down Expand Up @@ -139,8 +97,6 @@ export class CloudEditor extends Block {
.then(({ width, height }) => {
this.$['*imageSize'] = { width, height };
});
await this._waitForSize();
this._loadImageFromCdn();
} catch (err) {
if (err) {
console.error('Failed to load image info', err);
Expand All @@ -152,4 +108,5 @@ export class CloudEditor extends Block {
CloudEditor.template = TEMPLATE;
CloudEditor.bindAttributes({
uuid: 'uuid',
'cdn-url': 'cdnUrl',
});
4 changes: 1 addition & 3 deletions blocks/CloudImageEditor/src/EditorFilterControl.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,7 @@ export class EditorFilterControl extends EditorButtonControl {
})
.finally(() => {
previewEl.style.backgroundImage = `url(${src})`;
setTimeout(() => {
previewEl.style.opacity = '1';
});
previewEl.setAttribute('loaded', '');

observer.unobserve(this);
});
Expand Down
99 changes: 57 additions & 42 deletions blocks/CloudImageEditor/src/EditorToolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ import { FAKE_ORIGINAL_FILTER } from './EditorSlider.js';
import { classNames } from './lib/classNames.js';
import { debounce } from './lib/debounce.js';
import { batchPreloadImages } from './lib/preloadImage.js';
import { ALL_COLOR_OPERATIONS, ALL_CROP_OPERATIONS, ALL_FILTERS, TabId, TABS } from './toolbar-constants.js';
import {
ALL_COLOR_OPERATIONS,
ALL_CROP_OPERATIONS,
ALL_FILTERS,
COLOR_OPERATIONS_CONFIG,
TabId,
TABS,
} from './toolbar-constants.js';
import { viewerImageSrc } from './util.js';

/** @param {String} id */
Expand Down Expand Up @@ -47,12 +54,10 @@ export class EditorToolbar extends Block {
/** @type {import('./types.js').LoadingOperations} */
'*loadingOperations': new Map(),
'*showSlider': false,
/** @type {import('./types.js').Transformations} */
'*editorTransformations': {},
'*currentFilter': FAKE_ORIGINAL_FILTER,
'*currentOperation': null,
'*tabId': TabId.CROP,
showLoader: false,
tabId: TabId.CROP,
filters: ALL_FILTERS,
colorOperations: ALL_COLOR_OPERATIONS,
cropOperations: ALL_CROP_OPERATIONS,
Expand Down Expand Up @@ -104,18 +109,11 @@ export class EditorToolbar extends Block {
this._debouncedShowLoader = debounce(this._showLoader.bind(this), 500);
}

get tabId() {
return this.$.tabId;
}

/** @private */
_onSliderClose() {
this.$['*showSlider'] = false;
if (this.$.tabId === TabId.SLIDERS) {
this.ref['tooltip-el'].className = classNames('filter-tooltip', {
'filter-tooltip_visible': false,
'filter-tooltip_hidden': true,
});
if (this.$['*tabId'] === TabId.SLIDERS) {
this.ref['tooltip-el'].classList.toggle('info-tooltip_visible', false);
}
}

Expand Down Expand Up @@ -193,7 +191,7 @@ export class EditorToolbar extends Block {
* @param {{ fromViewer?: Boolean }} options
*/
_activateTab(id, { fromViewer }) {
this.$.tabId = id;
this.$['*tabId'] = id;

if (id === TabId.CROP) {
this.$['*faderEl'].deactivate();
Expand Down Expand Up @@ -232,7 +230,7 @@ export class EditorToolbar extends Block {

/** @private */
_syncTabIndicator() {
let tabToggleEl = this.ref[`tab-toggle-${this.$.tabId}`];
let tabToggleEl = this.ref[`tab-toggle-${this.$['*tabId']}`];
let indicatorEl = this.ref['tabs-indicator'];
indicatorEl.style.transform = `translateX(${tabToggleEl.offsetLeft}px)`;
}
Expand All @@ -256,6 +254,31 @@ export class EditorToolbar extends Block {
this.$.showLoader = show;
}

_updateInfoTooltip = debounce(() => {
let transformations = this.$['*editorTransformations'];
let text = '';
let visible = false;

if (this.$['*tabId'] === TabId.FILTERS) {
visible = true;
if (this.$['*currentFilter'] && transformations?.filter?.name === this.$['*currentFilter']) {
let value = transformations?.filter?.amount || 100;
text = this.l10n(this.$['*currentFilter']) + ' ' + value;
} else {
text = this.l10n(FAKE_ORIGINAL_FILTER);
}
} else if (this.$['*tabId'] === TabId.SLIDERS && this.$['*currentOperation']) {
visible = true;
let value =
transformations?.[this.$['*currentOperation']] || COLOR_OPERATIONS_CONFIG[this.$['*currentOperation']].zero;
text = this.$['*currentOperation'] + ' ' + value;
}
if (visible) {
this.$['*operationTooltip'] = text;
}
this.ref['tooltip-el'].classList.toggle('info-tooltip_visible', visible);
}, 0);

initCallback() {
super.initCallback();

Expand All @@ -264,38 +287,28 @@ export class EditorToolbar extends Block {
this.sub('*imageSize', (imageSize) => {
if (imageSize) {
setTimeout(() => {
this._activateTab(this.$.tabId, { fromViewer: true });
this._activateTab(this.$['*tabId'], { fromViewer: true });
}, 0);
}
});

this.sub('*currentFilter', (currentFilter) => {
this.$['*operationTooltip'] = this.l10n(currentFilter || FAKE_ORIGINAL_FILTER);
this.ref['tooltip-el'].className = classNames('filter-tooltip', {
'filter-tooltip_visible': currentFilter,
'filter-tooltip_hidden': !currentFilter,
});
this.sub('*editorTransformations', (editorTransformations) => {
let appliedFilter = editorTransformations?.filter?.name;
if (this.$['*currentFilter'] !== appliedFilter) {
this.$['*currentFilter'] = appliedFilter;
}
});

this.sub('*currentOperation', (currentOperation) => {
if (this.$.tabId !== TabId.SLIDERS) {
return;
}
this.$['*operationTooltip'] = currentOperation;
this.ref['tooltip-el'].className = classNames('filter-tooltip', {
'filter-tooltip_visible': currentOperation,
'filter-tooltip_hidden': !currentOperation,
});
this.sub('*currentFilter', () => {
this._updateInfoTooltip();
});

this.sub('*tabId', (tabId) => {
if (tabId === TabId.FILTERS) {
this.$['*operationTooltip'] = this.$['*currentFilter'];
}
this.ref['tooltip-el'].className = classNames('filter-tooltip', {
'filter-tooltip_visible': tabId === TabId.FILTERS,
'filter-tooltip_hidden': tabId !== TabId.FILTERS,
});
this.sub('*currentOperation', () => {
this._updateInfoTooltip();
});

this.sub('*tabId', () => {
this._updateInfoTooltip();
});

this.sub('*originalUrl', (originalUrl) => {
Expand Down Expand Up @@ -329,14 +342,16 @@ export class EditorToolbar extends Block {
this.$['presence.subToolbar'] = showSlider;
this.$['presence.mainToolbar'] = !showSlider;
});

this._updateInfoTooltip();
}
}

EditorToolbar.template = /* HTML */ `
<lr-line-loader-ui set="active: showLoader"></lr-line-loader-ui>
<div class="filter-tooltip_container">
<div class="filter-tooltip_wrapper">
<div ref="tooltip-el" class="filter-tooltip filter-tooltip_visible">{{*operationTooltip}}</div>
<div class="info-tooltip_container">
<div class="info-tooltip_wrapper">
<div ref="tooltip-el" class="info-tooltip info-tooltip_hidden">{{*operationTooltip}}</div>
</div>
</div>
<div class="toolbar-container">
Expand Down
Loading

0 comments on commit 32b1858

Please sign in to comment.