@uploadcare/blocks
Version:
Building blocks for Uploadcare products integration
365 lines (327 loc) • 11 kB
JavaScript
import { Block } from '../../../abstract/Block.js';
import { EditorCropButtonControl } from './EditorCropButtonControl.js';
import { EditorFilterControl } from './EditorFilterControl.js';
import { EditorOperationControl } from './EditorOperationControl.js';
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 { viewerImageSrc } from './util.js';
/** @param {String} id */
function renderTabToggle(id) {
return /* HTML */ `
<lr-btn-ui
theme="boring"
ref="tab-toggle-${id}"
data-id="${id}"
icon="${id}"
tabindex="0"
set="onclick: on.clickTab;"
>
</lr-btn-ui>
`;
}
/** @param {String} id */
function renderTabContent(id) {
return /* HTML */ `
<lr-presence-toggle class="tab-content" set="visible: presence.tabContent.${id}; styles: presence.tabContentStyles">
<lr-editor-scroller hidden-scrollbar>
<div class="controls-list_align">
<div class="controls-list_inner" ref="controls-list-${id}"></div>
</div>
</lr-editor-scroller>
</lr-presence-toggle>
`;
}
export class EditorToolbar extends Block {
constructor() {
super();
this.init$ = {
...this.ctxInit,
'*sliderEl': null,
/** @type {import('./types.js').LoadingOperations} */
'*loadingOperations': new Map(),
'*showSlider': false,
/** @type {import('./types.js').Transformations} */
'*editorTransformations': {},
'*currentFilter': FAKE_ORIGINAL_FILTER,
'*currentOperation': null,
showLoader: false,
tabId: TabId.CROP,
filters: ALL_FILTERS,
colorOperations: ALL_COLOR_OPERATIONS,
cropOperations: ALL_CROP_OPERATIONS,
'*operationTooltip': null,
'l10n.cancel': this.l10n('cancel'),
'l10n.apply': this.l10n('apply'),
'presence.mainToolbar': true,
'presence.subToolbar': false,
'presence.tabContent.crop': false,
'presence.tabContent.sliders': false,
'presence.tabContent.filters': false,
'presence.subTopToolbarStyles': {
hidden: 'sub-toolbar--top-hidden',
visible: 'sub-toolbar--visible',
},
'presence.subBottomToolbarStyles': {
hidden: 'sub-toolbar--bottom-hidden',
visible: 'sub-toolbar--visible',
},
'presence.tabContentStyles': {
hidden: 'tab-content--hidden',
visible: 'tab-content--visible',
},
'on.cancel': (e) => {
this._cancelPreload && this._cancelPreload();
this.$['*on.cancel']();
},
'on.apply': (e) => {
this.$['*on.apply'](this.$['*editorTransformations']);
},
'on.applySlider': (e) => {
this.ref['slider-el'].apply();
this._onSliderClose();
},
'on.cancelSlider': (e) => {
this.ref['slider-el'].cancel();
this._onSliderClose();
},
'on.clickTab': (e) => {
let id = e.currentTarget.getAttribute('data-id');
this._activateTab(id, { fromViewer: false });
},
};
/** @private */
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,
});
}
}
/**
* @private
* @param {String} operation
*/
_createOperationControl(operation) {
let el = EditorOperationControl.is && new EditorOperationControl();
el['operation'] = operation;
return el;
}
/**
* @private
* @param {String} filter
*/
_createFilterControl(filter) {
let el = EditorFilterControl.is && new EditorFilterControl();
el['filter'] = filter;
return el;
}
/**
* @private
* @param {String} operation
*/
_createToggleControl(operation) {
let el = EditorCropButtonControl.is && new EditorCropButtonControl();
el['operation'] = operation;
return el;
}
/**
* @private
* @param {String} tabId
*/
_renderControlsList(tabId) {
let listEl = this.ref[`controls-list-${tabId}`];
let fr = document.createDocumentFragment();
if (tabId === TabId.CROP) {
this.$.cropOperations.forEach((operation) => {
let el = this._createToggleControl(operation);
// @ts-ignore
fr.appendChild(el);
});
} else if (tabId === TabId.FILTERS) {
[FAKE_ORIGINAL_FILTER, ...this.$.filters].forEach((filterId) => {
let el = this._createFilterControl(filterId);
// @ts-ignore
fr.appendChild(el);
});
} else if (tabId === TabId.SLIDERS) {
this.$.colorOperations.forEach((operation) => {
let el = this._createOperationControl(operation);
// @ts-ignore
fr.appendChild(el);
});
}
fr.childNodes.forEach((/** @type {HTMLElement} */ el, idx) => {
if (idx === fr.childNodes.length - 1) {
el.classList.add('controls-list_last-item');
}
});
listEl.innerHTML = '';
listEl.appendChild(fr);
}
/**
* @private
* @param {String} id
* @param {{ fromViewer?: Boolean }} options
*/
_activateTab(id, { fromViewer }) {
this.$.tabId = id;
if (id === TabId.CROP) {
this.$['*faderEl'].deactivate();
this.$['*cropperEl'].activate(this.$['*imageSize'], { fromViewer });
} else {
this.$['*faderEl'].activate({ url: this.$['*originalUrl'], fromViewer });
this.$['*cropperEl'].deactivate();
}
for (let tabId of TABS) {
let isCurrentTab = tabId === id;
let tabToggleEl = this.ref[`tab-toggle-${tabId}`];
tabToggleEl.active = isCurrentTab;
if (isCurrentTab) {
this._renderControlsList(id);
this._syncTabIndicator();
} else {
this._unmountTabControls(tabId);
}
this.$[`presence.tabContent.${tabId}`] = isCurrentTab;
}
}
/**
* @private
* @param {String} tabId
*/
_unmountTabControls(tabId) {
let listEl = this.ref[`controls-list-${tabId}`];
if (listEl) {
listEl.innerHTML = '';
}
}
/** @private */
_syncTabIndicator() {
let tabToggleEl = this.ref[`tab-toggle-${this.$.tabId}`];
let indicatorEl = this.ref['tabs-indicator'];
indicatorEl.style.transform = `translateX(${tabToggleEl.offsetLeft}px)`;
}
/** @private */
_preloadEditedImage() {
if (this.$['*imgContainerEl'] && this.$['*originalUrl']) {
let width = this.$['*imgContainerEl'].offsetWidth;
let src = this.proxyUrl(viewerImageSrc(this.$['*originalUrl'], width, this.$['*editorTransformations']));
this._cancelPreload && this._cancelPreload();
let { cancel } = batchPreloadImages([src]);
this._cancelPreload = () => {
cancel();
this._cancelPreload = undefined;
};
}
}
/** @private */
_showLoader(show) {
this.$.showLoader = show;
}
initCallback() {
super.initCallback();
this.$['*sliderEl'] = this.ref['slider-el'];
this.sub('*imageSize', (imageSize) => {
if (imageSize) {
setTimeout(() => {
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('*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('*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('*originalUrl', (originalUrl) => {
this.$['*faderEl'] && this.$['*faderEl'].deactivate();
});
this.sub('*editorTransformations', (transformations) => {
this._preloadEditedImage();
if (this.$['*faderEl']) {
this.$['*faderEl'].setTransformations(transformations);
}
});
this.sub('*loadingOperations', (/** @type {import('./types.js').LoadingOperations} */ loadingOperations) => {
let anyLoading = false;
for (let [, mapping] of loadingOperations.entries()) {
if (anyLoading) {
break;
}
for (let [, loading] of mapping.entries()) {
if (loading) {
anyLoading = true;
break;
}
}
}
this._debouncedShowLoader(anyLoading);
});
this.sub('*showSlider', (showSlider) => {
this.$['presence.subToolbar'] = showSlider;
this.$['presence.mainToolbar'] = !showSlider;
});
}
}
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>
</div>
<div class="toolbar-container">
<lr-presence-toggle class="sub-toolbar" set="visible: presence.mainToolbar; styles: presence.subTopToolbarStyles">
<div class="tab-content-row">${TABS.map(renderTabContent).join('')}</div>
<div class="controls-row">
<lr-btn-ui theme="boring" icon="closeMax" set="onclick: on.cancel"> </lr-btn-ui>
<div class="tab-toggles">
<div ref="tabs-indicator" class="tab-toggles_indicator"></div>
${TABS.map(renderTabToggle).join('')}
</div>
<lr-btn-ui theme="primary" icon="done" set="onclick: on.apply"> </lr-btn-ui>
</div>
</lr-presence-toggle>
<lr-presence-toggle class="sub-toolbar" set="visible: presence.subToolbar; styles: presence.subBottomToolbarStyles">
<div class="slider">
<lr-editor-slider ref="slider-el"></lr-editor-slider>
</div>
<div class="controls-row">
<lr-btn-ui theme="boring" set="@text: l10n.cancel; onclick: on.cancelSlider;"> </lr-btn-ui>
<lr-btn-ui theme="primary" set="@text: l10n.apply; onclick: on.applySlider;"> </lr-btn-ui>
</div>
</lr-presence-toggle>
</div>
`;