Skip to content

Commit

Permalink
feat(public-upload-api): allow to switch activity to the cloud image …
Browse files Browse the repository at this point in the history
…editor with predefined file opened
  • Loading branch information
nd0ut committed Aug 7, 2024
1 parent c1907a8 commit ef663fa
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 50 deletions.
25 changes: 19 additions & 6 deletions abstract/ActivityBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import { activityBlockCtx } from './CTX.js';
const ACTIVE_ATTR = 'active';
const ACTIVE_PROP = '___ACTIVITY_IS_ACTIVE___';

/**
* @typedef {{
* 'cloud-image-edit': import('../blocks/CloudImageEditorActivity/CloudImageEditorActivity.js').ActivityParams;
* external: import('../blocks/ExternalSource/ExternalSource.js').ActivityParams;
* }} ActivityParamsMap
*/

export class ActivityBlock extends Block {
/** @protected */
historyTracked = false;
Expand Down Expand Up @@ -54,10 +61,15 @@ export class ActivityBlock extends Block {
this.setAttribute('activity', this.activityType);
}
this.sub('*currentActivity', (/** @type {String} */ val) => {
if (this.activityType !== val && this[ACTIVE_PROP]) {
this._deactivate();
} else if (this.activityType === val && !this[ACTIVE_PROP]) {
this._activate();
try {
if (this.activityType !== val && this[ACTIVE_PROP]) {
this._deactivate();
} else if (this.activityType === val && !this[ACTIVE_PROP]) {
this._activate();
}
} catch (err) {
console.error(`Error in activity "${this.activityType}". `, err);
this.$['*currentActivity'] = this.$['*history'][this.$['*history'].length - 1] ?? null;
}

if (!val) {
Expand Down Expand Up @@ -156,6 +168,7 @@ export class ActivityBlock extends Block {
return this.ctxName + this.activityType;
}

/** @type {ActivityParamsMap[keyof ActivityParamsMap]} */
get activityParams() {
return this.$['*currentActivityParams'];
}
Expand Down Expand Up @@ -201,7 +214,7 @@ ActivityBlock.activities = Object.freeze({
URL: 'url',
CLOUD_IMG_EDIT: 'cloud-image-edit',
EXTERNAL: 'external',
DETAILS: 'details',
});

/** @typedef {(typeof ActivityBlock)['activities'][keyof (typeof ActivityBlock)['activities']] | (string & {}) | null} ActivityType */
/** @typedef {(typeof ActivityBlock)['activities'][keyof (typeof ActivityBlock)['activities']]} RegisteredActivityType */
/** @typedef {RegisteredActivityType | (string & {}) | null} ActivityType */
1 change: 0 additions & 1 deletion abstract/CTX.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export const uploaderBlockCtx = (fnCtx) => ({
...activityBlockCtx(fnCtx),
'*commonProgress': 0,
'*uploadList': [],
'*focusedEntry': null,
'*uploadQueue': new Queue(1),
/** @type {ReturnType<import('../types').OutputErrorCollection>[]} */
'*collectionErrors': [],
Expand Down
4 changes: 3 additions & 1 deletion abstract/UploaderBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,9 @@ export class UploaderBlock extends ActivityBlock {
this.cfg.useCloudImageEditor &&
this.hasBlockInCtx((block) => block.activityType === ActivityBlock.activities.CLOUD_IMG_EDIT)
) {
this.$['*focusedEntry'] = entry;
this.$['*currentActivityParams'] = {
internalId: entry.uid,
};
this.$['*currentActivity'] = ActivityBlock.activities.CLOUD_IMG_EDIT;
}
}
Expand Down
14 changes: 10 additions & 4 deletions abstract/UploaderPublicApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,13 +278,19 @@ export class UploaderPublicApi {
};

/**
* @param {import('./ActivityBlock.js').ActivityType} activityType
* @param {import('../blocks/ExternalSource/ExternalSource.js').ActivityParams | {}} [params]
* @type {<T extends import('./ActivityBlock.js').ActivityType>(
* activityType: T,
* ...params: T extends keyof import('./ActivityBlock.js').ActivityParamsMap
* ? [import('./ActivityBlock.js').ActivityParamsMap[T]]
* : T extends import('./ActivityBlock.js').RegisteredActivityType
* ? [undefined?]
* : [any?]
* ) => void}
*/
setCurrentActivity = (activityType, params = {}) => {
setCurrentActivity = (activityType, params = undefined) => {
if (this._ctx.hasBlockInCtx((b) => b.activityType === activityType)) {
this._ctx.set$({
'*currentActivityParams': params,
'*currentActivityParams': params ?? {},
'*currentActivity': activityType,
});
return;
Expand Down
59 changes: 34 additions & 25 deletions blocks/CloudImageEditorActivity/CloudImageEditorActivity.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,31 @@ import { ActivityBlock } from '../../abstract/ActivityBlock.js';
import { UploaderBlock } from '../../abstract/UploaderBlock.js';
import { CloudImageEditorBlock } from '../CloudImageEditor/index.js';

/** @typedef {{ internalId: string }} ActivityParams */

export class CloudImageEditorActivity extends UploaderBlock {
couldBeCtxOwner = true;
activityType = ActivityBlock.activities.CLOUD_IMG_EDIT;

constructor() {
super();

this.init$ = {
...this.init$,
cdnUrl: null,
};
/**
* @private
* @type {import('../../abstract/TypedData.js').TypedData | undefined}
*/
_entry;

/**
* @private
* @type {CloudImageEditorBlock | undefined}
*/
_instance;

/** @type {ActivityParams} */
get activityParams() {
const params = super.activityParams;
if ('internalId' in params) {
return params;
}
throw new Error(`Cloud Image Editor activity params not found`);
}

initCallback() {
Expand All @@ -24,19 +38,6 @@ export class CloudImageEditorActivity extends UploaderBlock {
onDeactivate: () => this.unmountEditor(),
});

this.sub('*focusedEntry', (/** @type {import('../../abstract/TypedData.js').TypedData} */ entry) => {
if (!entry) {
return;
}
this.entry = entry;

this.entry.subscribe('cdnUrl', (cdnUrl) => {
if (cdnUrl) {
this.$.cdnUrl = cdnUrl;
}
});
});

this.subConfigValue('cropPreset', (cropPreset) => {
if (this._instance && this._instance.getAttribute('crop-preset') !== cropPreset) {
this._instance.setAttribute('crop-preset', cropPreset);
Expand All @@ -52,11 +53,11 @@ export class CloudImageEditorActivity extends UploaderBlock {

/** @param {CustomEvent<import('../CloudImageEditor/src/types.js').ApplyResult>} e */
handleApply(e) {
if (!this.entry) {
if (!this._entry) {
return;
}
let result = e.detail;
this.entry.setMultipleValues({
this._entry.setMultipleValues({
cdnUrl: result.cdnUrl,
cdnUrlModifiers: result.cdnUrlModifiers,
});
Expand All @@ -68,8 +69,17 @@ export class CloudImageEditorActivity extends UploaderBlock {
}

mountEditor() {
const { internalId } = this.activityParams;
this._entry = this.uploadCollection.read(internalId);
if (!this._entry) {
throw new Error(`Entry with internalId "${internalId}" not found`);
}
const cdnUrl = this._entry.getValue('cdnUrl');
if (!cdnUrl) {
throw new Error(`Entry with internalId "${internalId}" hasn't uploaded yet`);
}

const instance = new CloudImageEditorBlock();
const cdnUrl = this.$.cdnUrl;
const cropPreset = this.cfg.cropPreset;
const tabs = this.cfg.cloudImageEditorTabs;

Expand Down Expand Up @@ -100,14 +110,13 @@ export class CloudImageEditorActivity extends UploaderBlock {

this.innerHTML = '';
this.appendChild(instance);
this._mounted = true;

/** @private */
this._instance = instance;
}

unmountEditor() {
this._instance = undefined;
this._entry = undefined;
this.innerHTML = '';
}
}
9 changes: 9 additions & 0 deletions blocks/ExternalSource/ExternalSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ export class ExternalSource extends UploaderBlock {
};
}

/** @type {ActivityParams} */
get activityParams() {
const params = super.activityParams;
if ('externalSourceType' in params) {
return params;
}
throw new Error(`External Source activity params not found`);
}

/**
* @private
* @type {HTMLIFrameElement | null}
Expand Down
12 changes: 4 additions & 8 deletions blocks/FileItem/FileItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,10 @@ export class FileItem extends UploaderBlock {
isEditable: false,
state: FileItemState.IDLE,
onEdit: () => {
this.set$({
'*focusedEntry': this._entry,
});
if (this.hasBlockInCtx((b) => b.activityType === ActivityBlock.activities.DETAILS)) {
this.$['*currentActivity'] = ActivityBlock.activities.DETAILS;
} else {
this.$['*currentActivity'] = ActivityBlock.activities.CLOUD_IMG_EDIT;
}
this.$['*currentActivityParams'] = {
internalId: this._entry.uid,
};
this.$['*currentActivity'] = ActivityBlock.activities.CLOUD_IMG_EDIT;
},
onRemove: () => {
this.uploadCollection.remove(this.$.uid);
Expand Down
34 changes: 34 additions & 0 deletions types/test/public-upload-api.test-d.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { UploadCtxProvider } from '../../index.js';

const instance = new UploadCtxProvider();
const api = instance.getAPI();

api.addFileFromUrl('https://example.com/image.png');

api.setCurrentActivity('camera');
api.setCurrentActivity('cloud-image-edit', { internalId: 'id' });
api.setCurrentActivity('external', {
externalSourceType: 'type',
});

// @ts-expect-error - should not allow to set activity without params
api.setCurrentActivity('cloud-image-edit');
// @ts-expect-error - should not allow to set activity without params
api.setCurrentActivity('external');

// @ts-expect-error - should not allow to set activity with invalid params
api.setCurrentActivity('camera', {
invalidParam: 'value',
});
api.setCurrentActivity('cloud-image-edit', {
// @ts-expect-error - should not allow to set activity with invalid params
invalidParam: 'value',
});
api.setCurrentActivity('external', {
// @ts-expect-error - should not allow to set activity with invalid params
invalidParam: 'value',
});

// should allow to set some custom activity
api.setCurrentActivity('my-custom-activity');
api.setCurrentActivity('my-custom-activity', { myCustomParam: 'value' });
9 changes: 4 additions & 5 deletions types/test/uc-upload-ctx-provider.test-d.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { expectNotType, expectType } from 'tsd';
import { UploadcareFile, UploadcareGroup } from '@uploadcare/upload-client';
import { useRef } from 'react';
import { expectType } from 'tsd';
import {
ActivityBlock,
EventMap,
OutputCollectionErrorType,
OutputCollectionState,
OutputCollectionStatus,
OutputError,
OutputFileEntry,
OutputFileErrorType,
UploadCtxProvider,
UploadCtxProvider
} from '../../index.js';
import { useRef } from 'react';
import { UploadcareFile, UploadcareGroup } from '@uploadcare/upload-client';

const instance = new UploadCtxProvider();
instance.uploadCollection.size;
Expand Down

0 comments on commit ef663fa

Please sign in to comment.