From 6fe5bcd8c95894c26b0e747db5817a2247347dfe Mon Sep 17 00:00:00 2001 From: Egor Didenko Date: Mon, 2 Sep 2024 11:59:24 -0400 Subject: [PATCH] feat(entry-file): added file upload source (#736) * feat(entry-file): added file upload source * feat: added method getSourceTypes * feat: added export UploadSource, ExternalUploadSource * feat: added types and handler fallback * build(ssr): added check object and return stubbedExports with obj * feat(types): added SourceTypes in the OutputFileEntry * test(types): added test for source type in the file --- abstract/UploaderBlock.js | 29 ++++++++------------ abstract/UploaderPublicApi.js | 1 + abstract/uploadEntrySchema.js | 1 + blocks/DropArea/DropArea.js | 2 +- blocks/FileItem/FileItem.js | 1 + blocks/SourceBtn/SourceBtn.js | 9 +++--- blocks/UrlSource/UrlSource.js | 2 +- blocks/utils/UploadSource.js | 21 +++++++++++++- build-ssr-stubs.js | 2 ++ index.js | 1 + types/exported.d.ts | 2 ++ types/test/uc-upload-ctx-provider.test-d.tsx | 2 ++ 12 files changed, 48 insertions(+), 25 deletions(-) diff --git a/abstract/UploaderBlock.js b/abstract/UploaderBlock.js index 93d4d40f4..408beafdb 100644 --- a/abstract/UploaderBlock.js +++ b/abstract/UploaderBlock.js @@ -15,6 +15,7 @@ import { TypedCollection } from './TypedCollection.js'; import { UploaderPublicApi } from './UploaderPublicApi.js'; import { ValidationManager } from './ValidationManager.js'; import { uploadEntrySchema } from './uploadEntrySchema.js'; +import { ExternalUploadSource, UploadSource } from '../blocks/utils/UploadSource.js'; export class UploaderBlock extends ActivityBlock { /** @protected */ @@ -421,26 +422,18 @@ export class UploaderBlock extends ActivityBlock { } } -/** @enum {String} */ +/** + * @deprecated Use list sources ExternalUploadSource from from blocks/utils/UploadSource.js + * @enum {String} + */ UploaderBlock.extSrcList = Object.freeze({ - FACEBOOK: 'facebook', - DROPBOX: 'dropbox', - GDRIVE: 'gdrive', - GPHOTOS: 'gphotos', - INSTAGRAM: 'instagram', - FLICKR: 'flickr', - VK: 'vk', - EVERNOTE: 'evernote', - BOX: 'box', - ONEDRIVE: 'onedrive', - HUDDLE: 'huddle', + ...ExternalUploadSource, }); -/** @enum {String} */ +/** + * @deprecated Use list sources UploadSource from from blocks/utils/UploadSource.js + * @enum {String} + */ UploaderBlock.sourceTypes = Object.freeze({ - LOCAL: 'local', - URL: 'url', - CAMERA: 'camera', - DRAW: 'draw', - ...UploaderBlock.extSrcList, + ...UploadSource, }); diff --git a/abstract/UploaderPublicApi.js b/abstract/UploaderPublicApi.js index 8de0c7d55..38f345ccc 100644 --- a/abstract/UploaderPublicApi.js +++ b/abstract/UploaderPublicApi.js @@ -240,6 +240,7 @@ export class UploaderPublicApi { isRemoved: status === 'removed', errors: /** @type {import('../types/exported.js').OutputFileEntry['errors']} */ (uploadEntryData.errors), status, + source: uploadEntryData?.source, }; return /** @type {import('../types/exported.js').OutputFileEntry} */ (outputItem); diff --git a/abstract/uploadEntrySchema.js b/abstract/uploadEntrySchema.js index 797384472..e43325abd 100644 --- a/abstract/uploadEntrySchema.js +++ b/abstract/uploadEntrySchema.js @@ -27,6 +27,7 @@ import { UploadcareFile } from '@uploadcare/upload-client'; * @property {string | null} fullPath * @property {import('@uploadcare/upload-client').Metadata | null} metadata * @property {boolean} isRemoved + * @property {String} source */ /** diff --git a/blocks/DropArea/DropArea.js b/blocks/DropArea/DropArea.js index b75785991..8c78e133e 100644 --- a/blocks/DropArea/DropArea.js +++ b/blocks/DropArea/DropArea.js @@ -151,7 +151,7 @@ export class DropArea extends UploaderBlock { this.subConfigValue('sourceList', (value) => { const list = stringToArray(value); // Enable drop area if local files are allowed - this.$.isEnabled = list.includes(UploaderBlock.sourceTypes.LOCAL); + this.$.isEnabled = list.includes(UploadSource.LOCAL); // Show drop area if it's enabled or default slot is overrided this.$.isVisible = this.$.isEnabled || !this.querySelector('[data-default-slot]'); }); diff --git a/blocks/FileItem/FileItem.js b/blocks/FileItem/FileItem.js index af46da0b5..e4e3d7ebe 100644 --- a/blocks/FileItem/FileItem.js +++ b/blocks/FileItem/FileItem.js @@ -397,6 +397,7 @@ export class FileItem extends UploaderBlock { cdnUrl: entry.getValue('cdnUrl') ?? fileInfo.cdnUrl, cdnUrlModifiers: entry.getValue('cdnUrlModifiers') ?? '', uploadProgress: 100, + source: entry.getValue('source') ?? null, }); if (entry === this._entry) { diff --git a/blocks/SourceBtn/SourceBtn.js b/blocks/SourceBtn/SourceBtn.js index 45e50f34a..50dc1c944 100644 --- a/blocks/SourceBtn/SourceBtn.js +++ b/blocks/SourceBtn/SourceBtn.js @@ -1,6 +1,7 @@ // @ts-check import { UploaderBlock } from '../../abstract/UploaderBlock.js'; import { ActivityBlock } from '../../abstract/ActivityBlock.js'; +import { ExternalUploadSource, UploadSource } from '../utils/UploadSource.js'; const L10N_PREFIX = 'src-type-'; @@ -37,19 +38,19 @@ export class SourceBtn extends UploaderBlock { initTypes() { this.registerType({ - type: UploaderBlock.sourceTypes.LOCAL, + type: UploadSource.LOCAL, activate: () => { this.api.openSystemDialog(); return false; }, }); this.registerType({ - type: UploaderBlock.sourceTypes.URL, + type: UploadSource.URL, activity: ActivityBlock.activities.URL, textKey: 'from-url', }); this.registerType({ - type: UploaderBlock.sourceTypes.CAMERA, + type: UploadSource.CAMERA, activity: ActivityBlock.activities.CAMERA, activate: () => { const supportsCapture = 'capture' in document.createElement('input'); @@ -65,7 +66,7 @@ export class SourceBtn extends UploaderBlock { icon: 'edit-draw', }); - for (let externalSourceType of Object.values(UploaderBlock.extSrcList)) { + for (let externalSourceType of Object.values(ExternalUploadSource)) { this.registerType({ type: externalSourceType, activity: ActivityBlock.activities.EXTERNAL, diff --git a/blocks/UrlSource/UrlSource.js b/blocks/UrlSource/UrlSource.js index 80b7d6fa9..df91d0529 100644 --- a/blocks/UrlSource/UrlSource.js +++ b/blocks/UrlSource/UrlSource.js @@ -13,7 +13,7 @@ export class UrlSource extends UploaderBlock { e.preventDefault(); let url = this.ref.input['value']; - this.api.addFileFromUrl(url, { source: UploadSource.URL_TAB }); + this.api.addFileFromUrl(url, { source: UploadSource.URL }); this.$['*currentActivity'] = ActivityBlock.activities.UPLOAD_LIST; }, onCancel: () => { diff --git a/blocks/utils/UploadSource.js b/blocks/utils/UploadSource.js index 46101d9c2..68e393912 100644 --- a/blocks/utils/UploadSource.js +++ b/blocks/utils/UploadSource.js @@ -1,8 +1,27 @@ +// @ts-check +export const ExternalUploadSource = Object.freeze({ + FACEBOOK: 'facebook', + DROPBOX: 'dropbox', + GDRIVE: 'gdrive', + GPHOTOS: 'gphotos', + INSTAGRAM: 'instagram', + FLICKR: 'flickr', + VK: 'vk', + EVERNOTE: 'evernote', + BOX: 'box', + ONEDRIVE: 'onedrive', + HUDDLE: 'huddle', +}); + export const UploadSource = Object.freeze({ LOCAL: 'local', DROP_AREA: 'drop-area', - URL_TAB: 'url-tab', CAMERA: 'camera', EXTERNAL: 'external', API: 'js-api', + URL: 'url', + DRAW: 'draw', + ...ExternalUploadSource, }); + +/** @typedef {(typeof UploadSource)[keyof typeof UploadSource]} SourceTypes */ diff --git a/build-ssr-stubs.js b/build-ssr-stubs.js index 0c382c729..31b347c62 100644 --- a/build-ssr-stubs.js +++ b/build-ssr-stubs.js @@ -72,6 +72,8 @@ const stubbedExports = Object.fromEntries( newValue = '() => {}'; } else if (typeof value === 'string') { newValue = `\`${value}\``; + } else if (typeof value === 'object') { + newValue = JSON.stringify(value); } else { throw new Error(`Unexpected export type: ${typeof value}`); } diff --git a/index.js b/index.js index 9a0fa516f..ed4d4c792 100644 --- a/index.js +++ b/index.js @@ -44,6 +44,7 @@ export { defineComponents } from './abstract/defineComponents.js'; export { defineLocale } from './abstract/localeRegistry.js'; export { loadFileUploaderFrom } from './abstract/loadFileUploaderFrom.js'; export { toKebabCase } from './utils/toKebabCase.js'; +export { UploadSource, ExternalUploadSource } from './blocks/utils/UploadSource.js'; export * from './env.js'; diff --git a/types/exported.d.ts b/types/exported.d.ts index d99654bf0..0f964bf02 100644 --- a/types/exported.d.ts +++ b/types/exported.d.ts @@ -21,6 +21,7 @@ export type SecureUploadsSignatureResolver = () => Promise string; export type FileValidators = FuncFileValidator[]; export type CollectionValidators = FuncCollectionValidator[]; +export type SourceTypes = import('../blocks/utils/UploadSource').SourceTypes export type ConfigType = { pubkey: string; @@ -168,6 +169,7 @@ export type OutputFileEntry externalUrl: string | null; uploadProgress: number; fullPath: string | null; + source: SourceTypes | null; } & ( | { status: 'success'; diff --git a/types/test/uc-upload-ctx-provider.test-d.tsx b/types/test/uc-upload-ctx-provider.test-d.tsx index 53a741126..a4c0ff11d 100644 --- a/types/test/uc-upload-ctx-provider.test-d.tsx +++ b/types/test/uc-upload-ctx-provider.test-d.tsx @@ -11,6 +11,7 @@ import { OutputFileErrorType, UploadCtxProvider } from '../../index.js'; +import { SourceTypes } from '../../blocks/utils/UploadSource.js'; const instance = new UploadCtxProvider(); instance.uploadCollection.size; @@ -96,6 +97,7 @@ instance.addEventListener('file-added', (e) => { expectType(state.cdnUrlModifiers); expectType(state.uuid); expectType(state.fileInfo); + expectType(state.source); }); instance.addEventListener('file-removed', (e) => {