Skip to content

Commit

Permalink
Merge pull request #527 from uploadcare/feat/editor-tab-toggles
Browse files Browse the repository at this point in the history
feat(cloud-image-editor): add ability to hide unnecessary editor tabs (crop, tuning or filters)
  • Loading branch information
nd0ut authored Oct 3, 2023
2 parents eb6cd99 + af15f51 commit 847d9ce
Show file tree
Hide file tree
Showing 15 changed files with 170 additions and 62 deletions.
5 changes: 3 additions & 2 deletions abstract/UploaderBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { uploaderBlockCtx } from './CTX.js';
import { EVENT_TYPES, EventData, EventManager } from './EventManager.js';
import { TypedCollection } from './TypedCollection.js';
import { uploadEntrySchema } from './uploadEntrySchema.js';
import { serializeCsv } from '../blocks/utils/comma-separated.js';

export class UploaderBlock extends ActivityBlock {
couldBeUploadCollectionOwner = false;
Expand Down Expand Up @@ -204,7 +205,7 @@ export class UploaderBlock extends ActivityBlock {

/** @param {{ captureCamera?: boolean }} options */
openSystemDialog(options = {}) {
let accept = mergeFileTypes([this.cfg.accept ?? '', ...(this.cfg.imgOnly ? IMAGE_ACCEPT_LIST : [])]).join(',');
let accept = serializeCsv(mergeFileTypes([this.cfg.accept ?? '', ...(this.cfg.imgOnly ? IMAGE_ACCEPT_LIST : [])]));

if (this.cfg.accept && !!this.cfg.imgOnly) {
console.warn(
Expand All @@ -218,7 +219,7 @@ export class UploaderBlock extends ActivityBlock {
this.fileInput.multiple = this.cfg.multiple;
if (options.captureCamera) {
this.fileInput.capture = '';
this.fileInput.accept = IMAGE_ACCEPT_LIST.join(',');
this.fileInput.accept = serializeCsv(IMAGE_ACCEPT_LIST);
} else {
this.fileInput.accept = accept;
}
Expand Down
9 changes: 9 additions & 0 deletions blocks/CloudImageEditor/src/CloudImageEditorBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { CloudImageEditorBase } from './CloudImageEditorBase.js';
import { classNames } from './lib/classNames.js';
import { parseCropPreset } from './lib/parseCropPreset.js';
import { operationsToTransformations, transformationsToOperations } from './lib/transformationUtils.js';
import { parseTabs } from './lib/parseTabs.js';
import { initState } from './state.js';
import { TEMPLATE } from './template.js';
import { TabId } from './toolbar-constants.js';
Expand Down Expand Up @@ -158,6 +159,13 @@ export class CloudImageEditorBlock extends CloudImageEditorBase {
this.$['*cropPresetList'] = parseCropPreset(val);
});

this.sub(
'tabs',
/** @param {string} val */ (val) => {
this.$['*tabList'] = parseTabs(val);
}
);

this.sub('*tabId', (tabId) => {
this.ref['img-el'].className = classNames('image', {
image_hidden_to_cropper: tabId === TabId.CROP,
Expand Down Expand Up @@ -210,4 +218,5 @@ CloudImageEditorBlock.bindAttributes({
uuid: 'uuid',
'cdn-url': 'cdnUrl',
'crop-preset': 'cropPreset',
tabs: 'tabs',
});
135 changes: 87 additions & 48 deletions blocks/CloudImageEditor/src/EditorToolbar.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-check
import { debounce } from '../../utils/debounce.js';
import { CloudImageEditorBase } from './CloudImageEditorBase.js';
import { EditorCropButtonControl } from './EditorCropButtonControl.js';
Expand All @@ -9,24 +10,26 @@ import {
ALL_COLOR_OPERATIONS,
ALL_CROP_OPERATIONS,
ALL_FILTERS,
ALL_TABS,
COLOR_OPERATIONS_CONFIG,
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>
<lr-presence-toggle class="tab-toggle" set="visible: presence.tabToggle.${id}; styles: presence.tabToggleStyles;">
<lr-btn-ui
theme="boring"
ref="tab-toggle-${id}"
data-id="${id}"
icon="${id}"
tabindex="0"
set="onclick: on.clickTab;"
>
</lr-btn-ui>
</lr-presence-toggle>
`;
}

Expand Down Expand Up @@ -67,9 +70,13 @@ export class EditorToolbar extends CloudImageEditorBase {

'presence.mainToolbar': true,
'presence.subToolbar': false,
'presence.tabToggles': true,
'presence.tabContent.crop': false,
'presence.tabContent.sliders': false,
'presence.tabContent.tuning': false,
'presence.tabContent.filters': false,
'presence.tabToggle.crop': true,
'presence.tabToggle.tuning': true,
'presence.tabToggle.filters': true,
'presence.subTopToolbarStyles': {
hidden: 'sub-toolbar--top-hidden',
visible: 'sub-toolbar--visible',
Expand All @@ -82,24 +89,35 @@ export class EditorToolbar extends CloudImageEditorBase {
hidden: 'tab-content--hidden',
visible: 'tab-content--visible',
},
'on.cancel': (e) => {
'presence.tabToggleStyles': {
hidden: 'tab-toggle--hidden',
visible: 'tab-toggle--visible',
},
'presence.tabTogglesStyles': {
hidden: 'tab-toggles--hidden',
visible: 'tab-toggles--visible',
},
'on.cancel': () => {
this._cancelPreload && this._cancelPreload();
this.$['*on.cancel']();
},
'on.apply': (e) => {
'on.apply': () => {
this.$['*on.apply'](this.$['*editorTransformations']);
},
'on.applySlider': (e) => {
'on.applySlider': () => {
this.ref['slider-el'].apply();
this._onSliderClose();
},
'on.cancelSlider': (e) => {
'on.cancelSlider': () => {
this.ref['slider-el'].cancel();
this._onSliderClose();
},
/** @param {MouseEvent} e */
'on.clickTab': (e) => {
let id = e.currentTarget.getAttribute('data-id');
this._activateTab(id, { fromViewer: false });
const id = /** @type {HTMLElement} */ (e.currentTarget).getAttribute('data-id');
if (id) {
this._activateTab(id, { fromViewer: false });
}
},
};

Expand All @@ -111,7 +129,7 @@ export class EditorToolbar extends CloudImageEditorBase {
/** @private */
_onSliderClose() {
this.$['*showSlider'] = false;
if (this.$['*tabId'] === TabId.SLIDERS) {
if (this.$['*tabId'] === TabId.TUNING) {
this.ref['tooltip-el'].classList.toggle('info-tooltip_visible', false);
}
}
Expand All @@ -121,8 +139,9 @@ export class EditorToolbar extends CloudImageEditorBase {
* @param {String} operation
*/
_createOperationControl(operation) {
let el = EditorOperationControl.is && new EditorOperationControl();
el['operation'] = operation;
let el = new EditorOperationControl();
// @ts-expect-error TODO: fix
el.operation = operation;
return el;
}

Expand All @@ -131,8 +150,9 @@ export class EditorToolbar extends CloudImageEditorBase {
* @param {String} filter
*/
_createFilterControl(filter) {
let el = EditorFilterControl.is && new EditorFilterControl();
el['filter'] = filter;
let el = new EditorFilterControl();
// @ts-expect-error TODO: fix
el.filter = filter;
return el;
}

Expand All @@ -141,8 +161,9 @@ export class EditorToolbar extends CloudImageEditorBase {
* @param {String} operation
*/
_createToggleControl(operation) {
let el = EditorCropButtonControl.is && new EditorCropButtonControl();
el['operation'] = operation;
let el = new EditorCropButtonControl();
// @ts-expect-error TODO: fix
el.operation = operation;
return el;
}

Expand All @@ -155,26 +176,30 @@ export class EditorToolbar extends CloudImageEditorBase {
let fr = document.createDocumentFragment();

if (tabId === TabId.CROP) {
this.$.cropOperations.forEach((operation) => {
let el = this._createToggleControl(operation);
// @ts-ignore
fr.appendChild(el);
});
this.$.cropOperations.forEach(
/** @param {string} operation */ (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);
});
} else if (tabId === TabId.TUNING) {
this.$.colorOperations.forEach(
/** @param {string} operation */ (operation) => {
let el = this._createOperationControl(operation);
// @ts-ignore
fr.appendChild(el);
}
);
}

fr.childNodes.forEach((/** @type {HTMLElement} */ el, idx) => {
[...fr.children].forEach((el, idx) => {
if (idx === fr.childNodes.length - 1) {
el.classList.add('controls-list_last-item');
}
Expand All @@ -200,7 +225,7 @@ export class EditorToolbar extends CloudImageEditorBase {
this.$['*cropperEl'].deactivate();
}

for (let tabId of TABS) {
for (let tabId of ALL_TABS) {
let isCurrentTab = tabId === id;

let tabToggleEl = this.ref[`tab-toggle-${tabId}`];
Expand Down Expand Up @@ -248,13 +273,18 @@ export class EditorToolbar extends CloudImageEditorBase {
}
}

/** @private */
/**
* @private
* @param {boolean} show
*/
_showLoader(show) {
this.$.showLoader = show;
}

_updateInfoTooltip = debounce(() => {
let transformations = this.$['*editorTransformations'];
const transformations = this.$['*editorTransformations'];
/** @type {keyof COLOR_OPERATIONS_CONFIG} */
const currentOperation = this.$['*currentOperation'];
let text = '';
let visible = false;

Expand All @@ -266,11 +296,10 @@ export class EditorToolbar extends CloudImageEditorBase {
} else {
text = this.l10n(FAKE_ORIGINAL_FILTER);
}
} else if (this.$['*tabId'] === TabId.SLIDERS && this.$['*currentOperation']) {
} else if (this.$['*tabId'] === TabId.TUNING && currentOperation) {
visible = true;
let value =
transformations?.[this.$['*currentOperation']] || COLOR_OPERATIONS_CONFIG[this.$['*currentOperation']].zero;
text = this.$['*currentOperation'] + ' ' + value;
let value = transformations?.[currentOperation] || COLOR_OPERATIONS_CONFIG[currentOperation].zero;
text = currentOperation + ' ' + value;
}
if (visible) {
this.$['*operationTooltip'] = text;
Expand Down Expand Up @@ -310,7 +339,7 @@ export class EditorToolbar extends CloudImageEditorBase {
this._updateInfoTooltip();
});

this.sub('*originalUrl', (originalUrl) => {
this.sub('*originalUrl', () => {
this.$['*faderEl'] && this.$['*faderEl'].deactivate();
});

Expand Down Expand Up @@ -342,6 +371,16 @@ export class EditorToolbar extends CloudImageEditorBase {
this.$['presence.mainToolbar'] = !showSlider;
});

this.sub('*tabList', (tabList) => {
this.$['presence.tabToggles'] = tabList.length > 1;
this.$['*tabId'] = tabList[0];
for (const tabId of ALL_TABS) {
this.$[`presence.tabToggle.${tabId}`] = tabList.includes(tabId);
const toggleEl = this.ref[`tab-toggle-${tabId}`];
toggleEl.style.gridColumn = tabList.indexOf(tabId) + 1;
}
});

this._updateInfoTooltip();
}
}
Expand All @@ -355,13 +394,13 @@ EditorToolbar.template = /* HTML */ `
</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="tab-content-row">${ALL_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">
<lr-presence-toggle class="tab-toggles" set="visible: presence.tabToggles; styles: presence.tabTogglesStyles">
<div ref="tabs-indicator" class="tab-toggles_indicator"></div>
${TABS.map(renderTabToggle).join('')}
</div>
${ALL_TABS.map(renderTabToggle).join('')}
</lr-presence-toggle>
<lr-btn-ui theme="primary" icon="done" set="onclick: on.apply"> </lr-btn-ui>
</div>
</lr-presence-toggle>
Expand Down
12 changes: 12 additions & 0 deletions blocks/CloudImageEditor/src/css/common.css
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,18 @@ lr-editor-toolbar > .toolbar-container > .sub-toolbar > .tab-content-row > .tab-
pointer-events: none;
}

lr-editor-toolbar > .toolbar-container > .sub-toolbar > .controls-row > .tab-toggles > .tab-toggle.tab-toggle--visible {
display: contents;
}

lr-editor-toolbar > .toolbar-container > .sub-toolbar > .controls-row > .tab-toggles > .tab-toggle.tab-toggle--hidden {
display: none;
}

lr-editor-toolbar > .toolbar-container > .sub-toolbar > .controls-row > .tab-toggles.tab-toggles--hidden {
display: none;
}

lr-editor-toolbar > .toolbar-container > .sub-toolbar > .tab-content-row > .tab-content .controls-list_align {
display: grid;
grid-template-areas: '. inner .';
Expand Down
2 changes: 1 addition & 1 deletion blocks/CloudImageEditor/src/css/icons.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
--icon-sad: 'M2 17c4.41828-4 11.5817-4 16 0M16.5 5c0 .55228-.4477 1-1 1s-1-.44772-1-1 .4477-1 1-1 1 .44772 1 1zm-11 0c0 .55228-.44772 1-1 1s-1-.44772-1-1 .44772-1 1-1 1 .44772 1 1z';
--icon-closeMax: 'M3 3l14 14m0-14L3 17';
--icon-crop: 'M20 14H7.00513C6.45001 14 6 13.55 6 12.9949V0M0 6h13.0667c.5154 0 .9333.41787.9333.93333V20M14.5.399902L13 1.9999l1.5 1.6M13 2h2c1.6569 0 3 1.34315 3 3v2M5.5 19.5999l1.5-1.6-1.5-1.6M7 18H5c-1.65685 0-3-1.3431-3-3v-2';
--icon-sliders: 'M8 10h11M1 10h4M1 4.5h11m3 0h4m-18 11h11m3 0h4M12 4.5a1.5 1.5 0 103 0 1.5 1.5 0 10-3 0M5 10a1.5 1.5 0 103 0 1.5 1.5 0 10-3 0M12 15.5a1.5 1.5 0 103 0 1.5 1.5 0 10-3 0';
--icon-tuning: 'M8 10h11M1 10h4M1 4.5h11m3 0h4m-18 11h11m3 0h4M12 4.5a1.5 1.5 0 103 0 1.5 1.5 0 10-3 0M5 10a1.5 1.5 0 103 0 1.5 1.5 0 10-3 0M12 15.5a1.5 1.5 0 103 0 1.5 1.5 0 10-3 0';
--icon-filters: 'M4.5 6.5a5.5 5.5 0 1011 0 5.5 5.5 0 10-11 0m-3.5 6a5.5 5.5 0 1011 0 5.5 5.5 0 10-11 0m7 0a5.5 5.5 0 1011 0 5.5 5.5 0 10-11 0';
--icon-done: 'M1 10.6316l5.68421 5.6842L19 4';
--icon-original: 'M0 40L40-.00000133';
Expand Down
14 changes: 14 additions & 0 deletions blocks/CloudImageEditor/src/lib/parseTabs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// @ts-check

import { deserealizeCsv } from '../../../utils/comma-separated.js';
import { ALL_TABS } from '../toolbar-constants.js';

/** @param {string} tabs */
export const parseTabs = (tabs) => {
if (!tabs) return ALL_TABS;
const tabList = deserealizeCsv(tabs).filter((tab) => ALL_TABS.includes(tab));
if (tabList.length === 0) {
return ALL_TABS;
}
return tabList;
};
4 changes: 4 additions & 0 deletions blocks/CloudImageEditor/src/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import { createCdnUrl, createCdnUrlModifiers } from '../../../utils/cdn-utils.js';
import { TRANSPARENT_PIXEL_SRC } from '../../../utils/transparentPixelSrc.js';
import { serializeCsv } from '../../utils/comma-separated.js';
import { transformationsToOperations } from './lib/transformationUtils.js';
import { ALL_TABS } from './toolbar-constants.js';

/** @param {import('./CloudImageEditorBlock.js').CloudImageEditorBlock} fnCtx */
export function initState(fnCtx) {
Expand All @@ -18,6 +20,7 @@ export function initState(fnCtx) {
'*editorTransformations': {},
/** @type {import('./types.js').CropPresetList} */
'*cropPresetList': [],
'*tabList': ALL_TABS,

entry: null,
extension: null,
Expand All @@ -33,6 +36,7 @@ export function initState(fnCtx) {
uuid: null,
cdnUrl: null,
cropPreset: '',
tabs: serializeCsv(ALL_TABS),

'presence.networkProblems': false,
'presence.modalCaption': true,
Expand Down
Loading

0 comments on commit 847d9ce

Please sign in to comment.