Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/aspect ratio #526

Merged
merged 7 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 94 additions & 45 deletions abstract/UploaderBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@
import { ActivityBlock } from './ActivityBlock.js';

import { Data } from '@symbiotejs/symbiote';
import { IMAGE_ACCEPT_LIST, mergeFileTypes, fileIsImage, matchMimeType, matchExtension } from '../utils/fileTypes.js';
import { uploadEntrySchema } from './uploadEntrySchema.js';
import { customUserAgent } from '../blocks/utils/userAgent.js';
import { TypedCollection } from './TypedCollection.js';
import { uploaderBlockCtx } from './CTX.js';
import { EVENT_TYPES, EventData, EventManager } from './EventManager.js';
import { calculateMaxCenteredCropFrame } from '../blocks/CloudImageEditor/src/crop-utils.js';
import { parseCropPreset } from '../blocks/CloudImageEditor/src/lib/parseCropPreset.js';
import { Modal } from '../blocks/Modal/Modal.js';
import { stringToArray } from '../utils/stringToArray.js';
import { warnOnce } from '../utils/warnOnce.js';
import { UploadSource } from '../blocks/utils/UploadSource.js';
import { prettyBytes } from '../utils/prettyBytes.js';
import { debounce } from '../blocks/utils/debounce.js';
import { customUserAgent } from '../blocks/utils/userAgent.js';
import { createCdnUrl, createCdnUrlModifiers } from '../utils/cdn-utils.js';
import { IMAGE_ACCEPT_LIST, fileIsImage, matchExtension, matchMimeType, mergeFileTypes } from '../utils/fileTypes.js';
import { prettyBytes } from '../utils/prettyBytes.js';
import { stringToArray } from '../utils/stringToArray.js';
import { warnOnce } from '../utils/warnOnce.js';
import { uploaderBlockCtx } from './CTX.js';
import { EVENT_TYPES, EventData, EventManager } from './EventManager.js';
import { TypedCollection } from './TypedCollection.js';
import { uploadEntrySchema } from './uploadEntrySchema.js';

export class UploaderBlock extends ActivityBlock {
couldBeUploadCollectionOwner = false;
Expand Down Expand Up @@ -457,6 +460,9 @@ export class UploaderBlock extends ActivityBlock {
);
}
if (changeMap.fileInfo) {
if (this.cfg.cropPreset) {
this.setInitialCrop();
}
let loadedItems = uploadCollection.findItems((entry) => {
return !!entry.getValue('fileInfo');
});
Expand Down Expand Up @@ -527,6 +533,43 @@ export class UploaderBlock extends ActivityBlock {
}
};

/** @private */
setInitialCrop() {
const cropPreset = parseCropPreset(this.cfg.cropPreset);
if (cropPreset) {
const [aspectRatioPreset] = cropPreset;

const entries = this.uploadCollection
.findItems(
(entry) =>
entry.getValue('fileInfo') &&
entry.getValue('isImage') &&
!entry.getValue('cdnUrlModifiers')?.includes('/crop/')
)
.map((id) => this.uploadCollection.read(id));

for (const entry of entries) {
const fileInfo = entry.getValue('fileInfo');
const { width, height } = fileInfo.imageInfo;
const expectedAspectRatio = aspectRatioPreset.width / aspectRatioPreset.height;
const crop = calculateMaxCenteredCropFrame(width, height, expectedAspectRatio);
const cdnUrlModifiers = createCdnUrlModifiers(`crop/${crop.width}x${crop.height}/${crop.x},${crop.y}`);
entry.setMultipleValues({
cdnUrlModifiers,
cdnUrl: createCdnUrl(entry.getValue('cdnUrl'), cdnUrlModifiers),
});
if (
this.uploadCollection.size === 1 &&
this.cfg.useCloudImageEditor &&
this.hasBlockInCtx((block) => block.activityType === ActivityBlock.activities.CLOUD_IMG_EDIT)
) {
this.$['*focusedEntry'] = entry;
this.$['*currentActivity'] = ActivityBlock.activities.CLOUD_IMG_EDIT;
}
}
}
}

/** @private */
async getMetadata() {
const configValue = this.cfg.metadata ?? /** @type {import('../types').Metadata} */ (this.$['*uploadMetadata']);
Expand Down Expand Up @@ -614,41 +657,47 @@ UploaderBlock.sourceTypes = Object.freeze({
});

Object.values(EVENT_TYPES).forEach((eType) => {
let eName = EventManager.eName(eType);
window.addEventListener(eName, (e) => {
let outputTypes = [EVENT_TYPES.UPLOAD_FINISH, EVENT_TYPES.REMOVE, EVENT_TYPES.CDN_MODIFICATION];
// @ts-ignore TODO: fix this
if (outputTypes.includes(e.detail.type)) {
const eName = EventManager.eName(eType);
const cb = debounce(
/** @param {CustomEvent} e */
(e) => {
let outputTypes = [EVENT_TYPES.UPLOAD_FINISH, EVENT_TYPES.REMOVE, EVENT_TYPES.CDN_MODIFICATION];
// @ts-ignore TODO: fix this
let dataCtx = Data.getCtx(e.detail.ctx);
/** @type {TypedCollection} */
let uploadCollection = dataCtx.read('uploadCollection');
// @ts-ignore TODO: fix this
let data = [];
uploadCollection.items().forEach((id) => {
let uploadEntryData = Data.getCtx(id).store;
/** @type {import('@uploadcare/upload-client').UploadcareFile} */
let fileInfo = uploadEntryData.fileInfo;
if (fileInfo) {
let outputItem = {
...fileInfo,
cdnUrlModifiers: uploadEntryData.cdnUrlModifiers,
cdnUrl: uploadEntryData.cdnUrl || fileInfo.cdnUrl,
};
data.push(outputItem);
}
});
EventManager.emit(
new EventData({
type: EVENT_TYPES.DATA_OUTPUT,
// @ts-ignore TODO: fix this
ctx: e.detail.ctx,
// @ts-ignore TODO: fix this
data,
})
);
// @ts-ignore TODO: fix this
dataCtx.pub('outputData', data);
}
});
if (outputTypes.includes(e.detail.type)) {
// @ts-ignore TODO: fix this
let dataCtx = Data.getCtx(e.detail.ctx);
/** @type {TypedCollection} */
let uploadCollection = dataCtx.read('uploadCollection');
// @ts-ignore TODO: fix this
let data = [];
uploadCollection.items().forEach((id) => {
let uploadEntryData = Data.getCtx(id).store;
/** @type {import('@uploadcare/upload-client').UploadcareFile} */
let fileInfo = uploadEntryData.fileInfo;
if (fileInfo) {
let outputItem = {
...fileInfo,
cdnUrlModifiers: uploadEntryData.cdnUrlModifiers,
cdnUrl: uploadEntryData.cdnUrl || fileInfo.cdnUrl,
};
data.push(outputItem);
}
});
EventManager.emit(
new EventData({
type: EVENT_TYPES.DATA_OUTPUT,
// @ts-ignore TODO: fix this
ctx: e.detail.ctx,
// @ts-ignore TODO: fix this
data,
})
);
// @ts-ignore TODO: fix this
dataCtx.pub('outputData', data);
}
},
0
);
// @ts-ignore TODO: fix this
window.addEventListener(eName, cb);
});
93 changes: 62 additions & 31 deletions blocks/CloudImageEditor/src/CloudImageEditorBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import {
extractUuid,
} from '../../../utils/cdn-utils.js';
import { TRANSPARENT_PIXEL_SRC } from '../../../utils/transparentPixelSrc.js';
import { debounce } from '../../utils/debounce.js';
import { CloudImageEditorBase } from './CloudImageEditorBase.js';
import { classNames } from './lib/classNames.js';
import { debounce } from './lib/debounce.js';
import { parseCropPreset } from './lib/parseCropPreset.js';
import { operationsToTransformations, transformationsToOperations } from './lib/transformationUtils.js';
import { initState } from './state.js';
import { TEMPLATE } from './template.js';
Expand All @@ -18,12 +19,14 @@ import { TabId } from './toolbar-constants.js';
export class CloudImageEditorBlock extends CloudImageEditorBase {
static className = 'cloud-image-editor';

// @ts-ignore TODO: fix this
init$ = {
...this.init$,
// @ts-ignore TODO: fix this
...initState(this),
};
constructor() {
super();

this.init$ = {
...this.init$,
...initState(this),
};
}

/** Force cloud editor to always use own context */
get ctxName() {
Expand Down Expand Up @@ -74,6 +77,48 @@ export class CloudImageEditorBlock extends CloudImageEditorBase {
this.initEditor();
}

async updateImage() {
await this._waitForSize();

if (this.$['*tabId'] === TabId.CROP) {
this.$['*cropperEl'].deactivate({ reset: true });
} else {
this.$['*faderEl'].deactivate();
}

this.$['*editorTransformations'] = {};

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.cfg.cdnCname, this.$.uuid);
} else {
throw new Error('No UUID nor CDN URL provided');
}

try {
const cdnUrl = createCdnUrl(this.$['*originalUrl'], createCdnUrlModifiers('json'));
const json = await fetch(cdnUrl).then((response) => response.json());

const { width, height } = /** @type {{ width: number; height: number }} */ (json);
this.$['*imageSize'] = { width, height };

if (this.$['*tabId'] === TabId.CROP) {
this.$['*cropperEl'].activate(this.$['*imageSize']);
} else {
this.$['*faderEl'].activate({ url: this.$['*originalUrl'] });
}
} catch (err) {
if (err) {
console.error('Failed to load image info', err);
}
}
}

async initEditor() {
try {
await this._waitForSize();
Expand Down Expand Up @@ -109,25 +154,17 @@ export class CloudImageEditorBlock extends CloudImageEditorBase {
}
});

this.sub('cropPreset', (val) => {
this.$['*cropPresetList'] = parseCropPreset(val);
});

this.sub('*tabId', (tabId) => {
this.ref['img-el'].className = classNames('image', {
image_hidden_to_cropper: tabId === TabId.CROP,
image_hidden_effects: tabId !== TabId.CROP,
});
});

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.cfg.cdnCname, this.$.uuid);
} else {
throw new Error('No UUID nor CDN URL provided');
}

this.classList.add('editor_ON');

this.sub('*networkProblems', (networkProblems) => {
Expand All @@ -138,6 +175,9 @@ export class CloudImageEditorBlock extends CloudImageEditorBase {
this.sub(
'*editorTransformations',
(transformations) => {
if (Object.keys(transformations).length === 0) {
return;
}
let originalUrl = this.$['*originalUrl'];
let cdnUrlModifiers = createCdnUrlModifiers(transformationsToOperations(transformations));
let cdnUrl = createCdnUrl(originalUrl, createCdnUrlModifiers(cdnUrlModifiers, 'preview'));
Expand All @@ -160,23 +200,14 @@ export class CloudImageEditorBlock extends CloudImageEditorBase {
false
);

try {
fetch(createCdnUrl(this.$['*originalUrl'], createCdnUrlModifiers('json')))
.then((response) => response.json())
.then((json) => {
const { width, height } = /** @type {{ width: number; height: number }} */ (json);
this.$['*imageSize'] = { width, height };
});
} catch (err) {
if (err) {
console.error('Failed to load image info', err);
}
}
this.sub('uuid', (val) => val && this.updateImage());
this.sub('cdnUrl', (val) => val && this.updateImage());
}
}

CloudImageEditorBlock.template = TEMPLATE;
CloudImageEditorBlock.bindAttributes({
uuid: 'uuid',
'cdn-url': 'cdnUrl',
'crop-preset': 'cropPreset',
});
Loading
Loading