Skip to content

Commit 3208859

Browse files
committed
refactor: tests / api changes
1 parent 0e4d99b commit 3208859

19 files changed

+479
-116
lines changed

src/abstract/CTX.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { UploadcareGroup } from '@uploadcare/upload-client';
22
import { Queue } from '@uploadcare/upload-client';
33
import type { LitBlock } from '../lit/LitBlock';
44
import type { OutputCollectionState, OutputErrorCollection } from '../types/index';
5-
import type { LazyPluginEntry } from './managers/plugin/lazyPluginRegistry';
5+
import type { LazyPluginEntry } from './managers/plugin/LazyPluginLoader';
66

77
export const blockCtx = () => ({});
88

src/abstract/UploaderPublicApi.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export class UploaderPublicApi extends SharedInstance {
121121
const internalId = this._uploadCollection.add({
122122
file,
123123
isImage: fileIsImage(file),
124-
mimeType: file.type,
124+
mimeType: file.type || null,
125125
fileName: fileName ?? file.name,
126126
fileSize: file.size,
127127
silent: silent ?? false,
@@ -362,7 +362,7 @@ export class UploaderPublicApi extends SharedInstance {
362362
}
363363
};
364364

365-
public async pluginsReady(): Promise<void> {
365+
private async _pluginsReady(): Promise<void> {
366366
const pluginManager = await this._sharedInstancesBag.wait('pluginManager');
367367
return pluginManager.pluginsReady();
368368
}
@@ -375,10 +375,7 @@ export class UploaderPublicApi extends SharedInstance {
375375
: [ActivityParamsMap[T]]
376376
: []
377377
) => {
378-
// Since Lit renders DOM asynchronously, we need to wait for the next tick to ensure that the activity block is rendered and registered in the context before we try to set it as current.
379-
// TODO: Do not rely on DOM rendering and block registration order, we should have a better way to handle this.
380-
// Some kind of ActivityManager that will keep track of registered activities and their states
381-
setTimeout(() => {
378+
void this._pluginsReady().then(() => {
382379
if (hasBlockInCtx(this._sharedInstancesBag.blocksRegistry, (b) => b.activityType === activityType)) {
383380
this._ctx.pub('*currentActivityParams', params[0] ?? {});
384381
this._ctx.pub('*currentActivity', activityType);
@@ -398,8 +395,7 @@ export class UploaderPublicApi extends SharedInstance {
398395
};
399396

400397
public setModalState = (opened: boolean): void => {
401-
// Next tick here because setCurrentActivity is also async and we want to make sure that the modal state is set
402-
setTimeout(() => {
398+
void this._pluginsReady().then(() => {
403399
if (opened && !this._ctx.read('*currentActivity')) {
404400
console.warn(`Can't open modal without current activity. Please use "setCurrentActivity" method first.`);
405401
return;

src/abstract/managers/plugin/LazyPluginLoader.ts

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,57 @@ import type { PubSub } from '../../../lit/PubSubCompat';
22
import type { SharedState } from '../../../lit/SharedState';
33
import type { ConfigType } from '../../../types/index';
44
import { sharedConfigKey } from '../../sharedConfigKey';
5-
import type { ConfigGetter, LazyPluginEntry } from './lazyPluginRegistry';
6-
import { withLazyPlugins } from './lazyPluginRegistry';
75
import type { UploaderPlugin } from './PluginTypes';
86

7+
export type ConfigGetter = <K extends keyof ConfigType>(key: K) => ConfigType[K];
8+
9+
export type LazyPluginEntry = {
10+
pluginId: string;
11+
configDeps: readonly (keyof ConfigType)[];
12+
isEnabled: (get: ConfigGetter) => boolean;
13+
load: () => UploaderPlugin | undefined | Promise<UploaderPlugin | undefined>;
14+
};
15+
16+
type ResolvedEntry = {
17+
pluginId: string;
18+
isEnabled: () => boolean;
19+
load: () => UploaderPlugin | undefined | Promise<UploaderPlugin | undefined>;
20+
};
21+
22+
const resolveLazyPlugins = async ({
23+
entries,
24+
signal,
25+
}: {
26+
entries: ResolvedEntry[];
27+
signal: AbortSignal;
28+
}): Promise<UploaderPlugin[]> => {
29+
const loadResults = await Promise.all(
30+
entries.map(async (entry): Promise<UploaderPlugin | undefined> => {
31+
if (!entry.isEnabled()) return undefined;
32+
try {
33+
const plugin = await entry.load();
34+
if (signal.aborted || !entry.isEnabled()) return undefined;
35+
return plugin ?? undefined;
36+
} catch (error) {
37+
if (!signal.aborted) {
38+
console.warn(`[${entry.pluginId}] Failed to load plugin`, error);
39+
}
40+
return undefined;
41+
}
42+
}),
43+
);
44+
45+
return loadResults.filter((p): p is UploaderPlugin => p !== undefined);
46+
};
47+
948
export class LazyPluginLoader {
1049
private _subs: Set<() => void> = new Set();
1150
private _unsubLazyPlugins: () => void;
1251
private _abortController?: AbortController;
1352

1453
public constructor(
1554
private readonly _ctx: PubSub<SharedState>,
16-
private readonly _onResolved: (plugins: UploaderPlugin[]) => void,
55+
private readonly _onCompute: (plugins: Promise<UploaderPlugin[] | undefined>) => void,
1756
) {
1857
this._unsubLazyPlugins = this._ctx.sub('*lazyPlugins', (entries) => {
1958
this._setEntries(entries ?? []);
@@ -49,19 +88,22 @@ export class LazyPluginLoader {
4988
const get: ConfigGetter = <K extends keyof ConfigType>(key: K) =>
5089
this._ctx.read(sharedConfigKey(key)) as unknown as ConfigType[K];
5190

52-
withLazyPlugins({
53-
plugins: () => get('plugins'),
91+
const lazyIds = new Set(entries.map((e) => e.pluginId));
92+
const userPlugins = (get('plugins') ?? []).filter((p) => !lazyIds.has(p?.id));
93+
94+
const pluginsPromise = resolveLazyPlugins({
5495
entries: entries.map((entry) => ({
5596
pluginId: entry.pluginId,
5697
isEnabled: () => entry.isEnabled(get),
5798
load: entry.load,
5899
})),
59100
signal: controller.signal,
60-
}).then((plugins) => {
61-
if (!controller.signal.aborted) {
62-
this._onResolved(plugins);
63-
}
101+
}).then((lazyPlugins) => {
102+
if (controller.signal.aborted) return undefined;
103+
return [...userPlugins, ...lazyPlugins];
64104
});
105+
106+
this._onCompute(pluginsPromise);
65107
}
66108

67109
public destroy(): void {

src/abstract/managers/plugin/PluginManager.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Unsubscriber } from '../../../lit/PubSubCompat';
22
import { SharedInstance, type SharedInstancesBag } from '../../../lit/shared-instances';
3+
import { fileIsImage } from '../../../utils/fileTypes';
34
import type { UploadEntryTypedData } from '../../uploadEntrySchema';
45
import { buildPluginApi } from './buildPluginApi';
56
import { LazyPluginLoader } from './LazyPluginLoader';
@@ -20,8 +21,10 @@ export class PluginManager extends SharedInstance {
2021
public constructor(sharedInstancesBag: SharedInstancesBag) {
2122
super(sharedInstancesBag);
2223

23-
this._lazyPluginLoader = new LazyPluginLoader(this._ctx, (plugins) => {
24-
this._pluginsUpdate = this._pluginsUpdate.then(() => this._syncPlugins(plugins));
24+
this._lazyPluginLoader = new LazyPluginLoader(this._ctx, (pluginsPromise) => {
25+
this._pluginsUpdate = this._pluginsUpdate
26+
.then(() => pluginsPromise)
27+
.then((plugins) => (plugins !== undefined ? this._syncPlugins(plugins) : undefined));
2528
});
2629
}
2730

@@ -129,10 +132,9 @@ export class PluginManager extends SharedInstance {
129132
if (onAddHooks.length === 0) return;
130133

131134
let file: File | Blob = initialFile;
132-
let mimeType = entry.getValue('mimeType');
133135
for (const hook of onAddHooks) {
134136
try {
135-
({ file, mimeType } = await hook.handler({ file, mimeType }));
137+
({ file } = await hook.handler({ file }));
136138
} catch (error) {
137139
this._debugPrint(`File hook "onAdd" from plugin "${hook.pluginId}" failed`, error);
138140
}
@@ -141,9 +143,11 @@ export class PluginManager extends SharedInstance {
141143
if (file !== initialFile) {
142144
entry.setValue('file', file as File);
143145
entry.setValue('fileSize', file.size);
144-
}
145-
if (mimeType !== entry.getValue('mimeType')) {
146-
entry.setValue('mimeType', mimeType);
146+
entry.setValue('mimeType', file.type || null);
147+
entry.setValue('isImage', fileIsImage(file));
148+
if (file instanceof File) {
149+
entry.setValue('fileName', file.name);
150+
}
147151
}
148152
}
149153

src/abstract/managers/plugin/PluginTypes.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ export type PluginFileActionRegistration = {
4848
export type PluginFileHookContext = {
4949
/** The file to transform */
5050
file: File | Blob;
51-
/** MIME type of the file */
52-
mimeType: string | null;
5351
};
5452

5553
export type PluginFileHookRegistration = {
@@ -58,7 +56,8 @@ export type PluginFileHookRegistration = {
5856
* - `'beforeUpload'`: called right before the file is uploaded.
5957
* - `'onAdd'`: called after the file is added to the upload list.
6058
*
61-
* Return the modified file and mimeType. Return the originals if no transformation is needed.
59+
* Return the (optionally transformed) file. After the hook runs, `mimeType`,
60+
* `isImage`, `fileSize`, and `fileName` are all re-derived from the returned file.
6261
*/
6362
type: 'beforeUpload' | 'onAdd';
6463
handler: (context: PluginFileHookContext) => PluginFileHookContext | Promise<PluginFileHookContext>;

src/abstract/managers/plugin/lazyPluginRegistry.ts

Lines changed: 0 additions & 60 deletions
This file was deleted.

src/blocks/Config/lazyPluginRegistry.ts

Lines changed: 0 additions & 2 deletions
This file was deleted.

src/blocks/FileItem/FileItem.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { property, state } from 'lit/decorators.js';
1010
import type { PluginFileActionRegistration } from '../../abstract/managers/plugin';
1111
import type { UploadEntryTypedData } from '../../abstract/uploadEntrySchema';
1212
import { debounce } from '../../utils/debounce';
13-
13+
import { fileIsImage } from '../../utils/fileTypes';
1414
import { throttle } from '../../utils/throttle';
1515
import { ExternalUploadSource } from '../../utils/UploadSource';
1616
import './file-item.css';
@@ -419,13 +419,15 @@ export class FileItem extends FileItemConfig {
419419
);
420420
for (const hook of beforeUploadHooks) {
421421
try {
422-
const { file: newFile, mimeType: newMimeType } = await hook.handler({
423-
file,
424-
mimeType: entry.getValue('mimeType'),
425-
});
426-
file = newFile;
427-
if (newMimeType !== entry.getValue('mimeType')) {
428-
entry.setValue('mimeType', newMimeType);
422+
const { file: newFile } = await hook.handler({ file });
423+
if (newFile !== file) {
424+
file = newFile;
425+
entry.setValue('mimeType', file.type || null);
426+
entry.setValue('isImage', fileIsImage(file));
427+
entry.setValue('fileSize', file.size);
428+
if (file instanceof File) {
429+
entry.setValue('fileName', file.name);
430+
}
429431
}
430432
} catch (error) {
431433
this.debugPrint(`File hook "beforeUpload" from plugin "${hook.pluginId}" failed`, error);

src/lit/LitSolutionBlock.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { html } from 'lit';
22
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
33
import { solutionBlockCtx } from '../abstract/CTX';
4-
import type { LazyPluginEntry } from '../abstract/managers/plugin/lazyPluginRegistry';
4+
import type { LazyPluginEntry } from '../abstract/managers/plugin/LazyPluginLoader';
55
import svgIconsSprite from '../blocks/themes/uc-basic/svg-sprite';
66
import { LitBlock } from './LitBlock';
77

src/lit/LitUploaderBlock.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export class LitUploaderBlock extends LitActivityBlock {
4949
return new TypedCollection<UploadEntryData>({
5050
initialValue: initialUploadEntryData,
5151
watchList: [
52+
'file',
5253
'uploadProgress',
5354
'uploadError',
5455
'fileInfo',
@@ -198,10 +199,7 @@ export class LitUploaderBlock extends LitActivityBlock {
198199
if (!entry.getValue('silent')) {
199200
this.emit(EventType.FILE_ADDED, this.api.getOutputItem(entry.uid));
200201
}
201-
const pluginManager = this._sharedInstancesBag.pluginManager;
202-
if (pluginManager) {
203-
void pluginManager.runOnAddHooks(entry);
204-
}
202+
void this._sharedInstancesBag.wait('pluginManager').then((pluginManager) => pluginManager.runOnAddHooks(entry));
205203
}
206204

207205
this.validationManager.runCollectionValidators();
@@ -238,7 +236,7 @@ export class LitUploaderBlock extends LitActivityBlock {
238236
const entriesToRunValidation = [
239237
...new Set(
240238
Object.entries(changeMap)
241-
.filter(([key]) => ['uploadError', 'fileInfo', 'cdnUrl', 'cdnUrlModifiers'].includes(key))
239+
.filter(([key]) => ['file', 'uploadError', 'fileInfo', 'cdnUrl', 'cdnUrlModifiers'].includes(key))
242240
.flatMap(([, ids]) => [...ids]),
243241
),
244242
];

0 commit comments

Comments
 (0)