Skip to content

Commit ef663fa

Browse files
committed
feat(public-upload-api): allow to switch activity to the cloud image editor with predefined file opened
1 parent c1907a8 commit ef663fa

File tree

9 files changed

+117
-50
lines changed

9 files changed

+117
-50
lines changed

abstract/ActivityBlock.js

+19-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ import { activityBlockCtx } from './CTX.js';
77
const ACTIVE_ATTR = 'active';
88
const ACTIVE_PROP = '___ACTIVITY_IS_ACTIVE___';
99

10+
/**
11+
* @typedef {{
12+
* 'cloud-image-edit': import('../blocks/CloudImageEditorActivity/CloudImageEditorActivity.js').ActivityParams;
13+
* external: import('../blocks/ExternalSource/ExternalSource.js').ActivityParams;
14+
* }} ActivityParamsMap
15+
*/
16+
1017
export class ActivityBlock extends Block {
1118
/** @protected */
1219
historyTracked = false;
@@ -54,10 +61,15 @@ export class ActivityBlock extends Block {
5461
this.setAttribute('activity', this.activityType);
5562
}
5663
this.sub('*currentActivity', (/** @type {String} */ val) => {
57-
if (this.activityType !== val && this[ACTIVE_PROP]) {
58-
this._deactivate();
59-
} else if (this.activityType === val && !this[ACTIVE_PROP]) {
60-
this._activate();
64+
try {
65+
if (this.activityType !== val && this[ACTIVE_PROP]) {
66+
this._deactivate();
67+
} else if (this.activityType === val && !this[ACTIVE_PROP]) {
68+
this._activate();
69+
}
70+
} catch (err) {
71+
console.error(`Error in activity "${this.activityType}". `, err);
72+
this.$['*currentActivity'] = this.$['*history'][this.$['*history'].length - 1] ?? null;
6173
}
6274

6375
if (!val) {
@@ -156,6 +168,7 @@ export class ActivityBlock extends Block {
156168
return this.ctxName + this.activityType;
157169
}
158170

171+
/** @type {ActivityParamsMap[keyof ActivityParamsMap]} */
159172
get activityParams() {
160173
return this.$['*currentActivityParams'];
161174
}
@@ -201,7 +214,7 @@ ActivityBlock.activities = Object.freeze({
201214
URL: 'url',
202215
CLOUD_IMG_EDIT: 'cloud-image-edit',
203216
EXTERNAL: 'external',
204-
DETAILS: 'details',
205217
});
206218

207-
/** @typedef {(typeof ActivityBlock)['activities'][keyof (typeof ActivityBlock)['activities']] | (string & {}) | null} ActivityType */
219+
/** @typedef {(typeof ActivityBlock)['activities'][keyof (typeof ActivityBlock)['activities']]} RegisteredActivityType */
220+
/** @typedef {RegisteredActivityType | (string & {}) | null} ActivityType */

abstract/CTX.js

-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export const uploaderBlockCtx = (fnCtx) => ({
2323
...activityBlockCtx(fnCtx),
2424
'*commonProgress': 0,
2525
'*uploadList': [],
26-
'*focusedEntry': null,
2726
'*uploadQueue': new Queue(1),
2827
/** @type {ReturnType<import('../types').OutputErrorCollection>[]} */
2928
'*collectionErrors': [],

abstract/UploaderBlock.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,9 @@ export class UploaderBlock extends ActivityBlock {
360360
this.cfg.useCloudImageEditor &&
361361
this.hasBlockInCtx((block) => block.activityType === ActivityBlock.activities.CLOUD_IMG_EDIT)
362362
) {
363-
this.$['*focusedEntry'] = entry;
363+
this.$['*currentActivityParams'] = {
364+
internalId: entry.uid,
365+
};
364366
this.$['*currentActivity'] = ActivityBlock.activities.CLOUD_IMG_EDIT;
365367
}
366368
}

abstract/UploaderPublicApi.js

+10-4
Original file line numberDiff line numberDiff line change
@@ -278,13 +278,19 @@ export class UploaderPublicApi {
278278
};
279279

280280
/**
281-
* @param {import('./ActivityBlock.js').ActivityType} activityType
282-
* @param {import('../blocks/ExternalSource/ExternalSource.js').ActivityParams | {}} [params]
281+
* @type {<T extends import('./ActivityBlock.js').ActivityType>(
282+
* activityType: T,
283+
* ...params: T extends keyof import('./ActivityBlock.js').ActivityParamsMap
284+
* ? [import('./ActivityBlock.js').ActivityParamsMap[T]]
285+
* : T extends import('./ActivityBlock.js').RegisteredActivityType
286+
* ? [undefined?]
287+
* : [any?]
288+
* ) => void}
283289
*/
284-
setCurrentActivity = (activityType, params = {}) => {
290+
setCurrentActivity = (activityType, params = undefined) => {
285291
if (this._ctx.hasBlockInCtx((b) => b.activityType === activityType)) {
286292
this._ctx.set$({
287-
'*currentActivityParams': params,
293+
'*currentActivityParams': params ?? {},
288294
'*currentActivity': activityType,
289295
});
290296
return;

blocks/CloudImageEditorActivity/CloudImageEditorActivity.js

+34-25
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,31 @@ import { ActivityBlock } from '../../abstract/ActivityBlock.js';
33
import { UploaderBlock } from '../../abstract/UploaderBlock.js';
44
import { CloudImageEditorBlock } from '../CloudImageEditor/index.js';
55

6+
/** @typedef {{ internalId: string }} ActivityParams */
7+
68
export class CloudImageEditorActivity extends UploaderBlock {
79
couldBeCtxOwner = true;
810
activityType = ActivityBlock.activities.CLOUD_IMG_EDIT;
911

10-
constructor() {
11-
super();
12-
13-
this.init$ = {
14-
...this.init$,
15-
cdnUrl: null,
16-
};
12+
/**
13+
* @private
14+
* @type {import('../../abstract/TypedData.js').TypedData | undefined}
15+
*/
16+
_entry;
17+
18+
/**
19+
* @private
20+
* @type {CloudImageEditorBlock | undefined}
21+
*/
22+
_instance;
23+
24+
/** @type {ActivityParams} */
25+
get activityParams() {
26+
const params = super.activityParams;
27+
if ('internalId' in params) {
28+
return params;
29+
}
30+
throw new Error(`Cloud Image Editor activity params not found`);
1731
}
1832

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

27-
this.sub('*focusedEntry', (/** @type {import('../../abstract/TypedData.js').TypedData} */ entry) => {
28-
if (!entry) {
29-
return;
30-
}
31-
this.entry = entry;
32-
33-
this.entry.subscribe('cdnUrl', (cdnUrl) => {
34-
if (cdnUrl) {
35-
this.$.cdnUrl = cdnUrl;
36-
}
37-
});
38-
});
39-
4041
this.subConfigValue('cropPreset', (cropPreset) => {
4142
if (this._instance && this._instance.getAttribute('crop-preset') !== cropPreset) {
4243
this._instance.setAttribute('crop-preset', cropPreset);
@@ -52,11 +53,11 @@ export class CloudImageEditorActivity extends UploaderBlock {
5253

5354
/** @param {CustomEvent<import('../CloudImageEditor/src/types.js').ApplyResult>} e */
5455
handleApply(e) {
55-
if (!this.entry) {
56+
if (!this._entry) {
5657
return;
5758
}
5859
let result = e.detail;
59-
this.entry.setMultipleValues({
60+
this._entry.setMultipleValues({
6061
cdnUrl: result.cdnUrl,
6162
cdnUrlModifiers: result.cdnUrlModifiers,
6263
});
@@ -68,8 +69,17 @@ export class CloudImageEditorActivity extends UploaderBlock {
6869
}
6970

7071
mountEditor() {
72+
const { internalId } = this.activityParams;
73+
this._entry = this.uploadCollection.read(internalId);
74+
if (!this._entry) {
75+
throw new Error(`Entry with internalId "${internalId}" not found`);
76+
}
77+
const cdnUrl = this._entry.getValue('cdnUrl');
78+
if (!cdnUrl) {
79+
throw new Error(`Entry with internalId "${internalId}" hasn't uploaded yet`);
80+
}
81+
7182
const instance = new CloudImageEditorBlock();
72-
const cdnUrl = this.$.cdnUrl;
7383
const cropPreset = this.cfg.cropPreset;
7484
const tabs = this.cfg.cloudImageEditorTabs;
7585

@@ -100,14 +110,13 @@ export class CloudImageEditorActivity extends UploaderBlock {
100110

101111
this.innerHTML = '';
102112
this.appendChild(instance);
103-
this._mounted = true;
104113

105-
/** @private */
106114
this._instance = instance;
107115
}
108116

109117
unmountEditor() {
110118
this._instance = undefined;
119+
this._entry = undefined;
111120
this.innerHTML = '';
112121
}
113122
}

blocks/ExternalSource/ExternalSource.js

+9
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ export class ExternalSource extends UploaderBlock {
6060
};
6161
}
6262

63+
/** @type {ActivityParams} */
64+
get activityParams() {
65+
const params = super.activityParams;
66+
if ('externalSourceType' in params) {
67+
return params;
68+
}
69+
throw new Error(`External Source activity params not found`);
70+
}
71+
6372
/**
6473
* @private
6574
* @type {HTMLIFrameElement | null}

blocks/FileItem/FileItem.js

+4-8
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,10 @@ export class FileItem extends UploaderBlock {
5656
isEditable: false,
5757
state: FileItemState.IDLE,
5858
onEdit: () => {
59-
this.set$({
60-
'*focusedEntry': this._entry,
61-
});
62-
if (this.hasBlockInCtx((b) => b.activityType === ActivityBlock.activities.DETAILS)) {
63-
this.$['*currentActivity'] = ActivityBlock.activities.DETAILS;
64-
} else {
65-
this.$['*currentActivity'] = ActivityBlock.activities.CLOUD_IMG_EDIT;
66-
}
59+
this.$['*currentActivityParams'] = {
60+
internalId: this._entry.uid,
61+
};
62+
this.$['*currentActivity'] = ActivityBlock.activities.CLOUD_IMG_EDIT;
6763
},
6864
onRemove: () => {
6965
this.uploadCollection.remove(this.$.uid);
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { UploadCtxProvider } from '../../index.js';
2+
3+
const instance = new UploadCtxProvider();
4+
const api = instance.getAPI();
5+
6+
api.addFileFromUrl('https://example.com/image.png');
7+
8+
api.setCurrentActivity('camera');
9+
api.setCurrentActivity('cloud-image-edit', { internalId: 'id' });
10+
api.setCurrentActivity('external', {
11+
externalSourceType: 'type',
12+
});
13+
14+
// @ts-expect-error - should not allow to set activity without params
15+
api.setCurrentActivity('cloud-image-edit');
16+
// @ts-expect-error - should not allow to set activity without params
17+
api.setCurrentActivity('external');
18+
19+
// @ts-expect-error - should not allow to set activity with invalid params
20+
api.setCurrentActivity('camera', {
21+
invalidParam: 'value',
22+
});
23+
api.setCurrentActivity('cloud-image-edit', {
24+
// @ts-expect-error - should not allow to set activity with invalid params
25+
invalidParam: 'value',
26+
});
27+
api.setCurrentActivity('external', {
28+
// @ts-expect-error - should not allow to set activity with invalid params
29+
invalidParam: 'value',
30+
});
31+
32+
// should allow to set some custom activity
33+
api.setCurrentActivity('my-custom-activity');
34+
api.setCurrentActivity('my-custom-activity', { myCustomParam: 'value' });

types/test/uc-upload-ctx-provider.test-d.tsx

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
import { expectNotType, expectType } from 'tsd';
1+
import { UploadcareFile, UploadcareGroup } from '@uploadcare/upload-client';
2+
import { useRef } from 'react';
3+
import { expectType } from 'tsd';
24
import {
35
ActivityBlock,
46
EventMap,
57
OutputCollectionErrorType,
6-
OutputCollectionState,
78
OutputCollectionStatus,
89
OutputError,
910
OutputFileEntry,
1011
OutputFileErrorType,
11-
UploadCtxProvider,
12+
UploadCtxProvider
1213
} from '../../index.js';
13-
import { useRef } from 'react';
14-
import { UploadcareFile, UploadcareGroup } from '@uploadcare/upload-client';
1514

1615
const instance = new UploadCtxProvider();
1716
instance.uploadCollection.size;

0 commit comments

Comments
 (0)