@uploadcare/file-uploader
Version:
Building blocks for Uploadcare products integration
264 lines (229 loc) • 7.73 kB
JavaScript
// @ts-check
import { ActivityBlock } from '../../abstract/ActivityBlock.js';
import { UploaderBlock } from '../../abstract/UploaderBlock.js';
import { EventType } from '../UploadCtxProvider/EventEmitter.js';
import { throttle } from '../utils/throttle.js';
/** @typedef {'grid' | 'list'} FilesViewMode */
/**
* @typedef {{
* total: number;
* succeed: number;
* uploading: number;
* failed: number;
* }} Summary
*/
export class UploadList extends UploaderBlock {
// Context owner should have access to CSS l10n
// TODO: We need to move away l10n from CSS
couldBeCtxOwner = true;
historyTracked = true;
activityType = ActivityBlock.activities.UPLOAD_LIST;
constructor() {
super();
this.init$ = {
...this.init$,
doneBtnVisible: false,
doneBtnEnabled: false,
uploadBtnVisible: false,
addMoreBtnVisible: false,
addMoreBtnEnabled: false,
headerText: '',
commonErrorMessage: '',
hasFiles: false,
onAdd: () => {
this.api.initFlow(true);
},
onUpload: () => {
this.emit(EventType.UPLOAD_CLICK);
this.api.uploadAll();
this._throttledHandleCollectionUpdate();
},
onDone: () => {
this.emit(EventType.DONE_CLICK, this.api.getOutputCollectionState());
this.api.doneFlow();
},
onCancel: () => {
this.uploadCollection.clearAll();
},
};
}
/** @private */
_throttledHandleCollectionUpdate = throttle(() => {
if (!this.isConnected) {
return;
}
this._updateUploadsState();
if (!this.couldOpenActivity && this.$['*currentActivity'] === this.activityType) {
this.historyBack();
}
}, 300);
/** @private */
_updateUploadsState() {
const collectionState = this.api.getOutputCollectionState();
/** @type {Summary} */
const summary = {
total: collectionState.totalCount,
succeed: collectionState.successCount,
uploading: collectionState.uploadingCount,
failed: collectionState.failedCount,
};
const fitCountRestrictions = !collectionState.errors.some(
(err) => err.type === 'TOO_MANY_FILES' || err.type === 'TOO_FEW_FILES',
);
const tooMany = collectionState.errors.some((err) => err.type === 'TOO_MANY_FILES');
const exact = collectionState.totalCount === (this.cfg.multiple ? this.cfg.multipleMax : 1);
const validationOk = summary.failed === 0 && collectionState.errors.length === 0;
let uploadBtnVisible = false;
let allDone = false;
let doneBtnEnabled = false;
const readyToUpload = summary.total - summary.succeed - summary.uploading - summary.failed;
if (readyToUpload > 0 && fitCountRestrictions) {
uploadBtnVisible = true;
} else {
allDone = true;
const groupOk = this.cfg.groupOutput ? !!collectionState.group : true;
doneBtnEnabled = summary.total === summary.succeed && fitCountRestrictions && validationOk && groupOk;
}
this.set$({
doneBtnVisible: allDone,
doneBtnEnabled: doneBtnEnabled,
uploadBtnVisible,
addMoreBtnEnabled: summary.total === 0 || (!tooMany && !exact),
addMoreBtnVisible: !exact || this.cfg.multiple,
});
this.bindL10n('headerText', () => this._getHeaderText(summary));
}
/**
* @private
* @param {Summary} summary
*/
_getHeaderText(summary) {
/** @param {keyof Summary} status */
const localizedText = (status) => {
const count = summary[status];
return this.l10n(`header-${status}`, {
count: count,
});
};
if (summary.uploading > 0) {
return localizedText('uploading');
}
if (summary.failed > 0) {
return localizedText('failed');
}
if (summary.succeed > 0) {
return localizedText('succeed');
}
return localizedText('total');
}
get couldOpenActivity() {
return this.cfg.showEmptyList || this.uploadCollection.size > 0;
}
initCallback() {
super.initCallback();
this.registerActivity(this.activityType);
this.subConfigValue('multiple', this._throttledHandleCollectionUpdate);
this.subConfigValue('multipleMin', this._throttledHandleCollectionUpdate);
this.subConfigValue('multipleMax', this._throttledHandleCollectionUpdate);
this.sub('*groupInfo', (groupInfo) => {
if (groupInfo) {
this._throttledHandleCollectionUpdate();
}
});
this.subConfigValue('filesViewMode', (mode) => {
this.setAttribute('mode', mode);
});
this.sub('*currentActivity', (currentActivity) => {
if (!this.couldOpenActivity && currentActivity === this.activityType) {
this.$['*currentActivity'] = this.initActivity;
}
});
// TODO: could be performance issue on many files
// there is no need to update buttons state on every progress tick
this.uploadCollection.observeProperties(this._throttledHandleCollectionUpdate);
this.sub(
'*uploadList',
(list) => {
this._throttledHandleCollectionUpdate();
this.set$({
hasFiles: list.length > 0,
});
if (!this.cfg.confirmUpload) {
this.api.uploadAll();
}
},
false,
);
this.sub(
'*collectionErrors',
/** @param {import('../../types').OutputError<import('../../types').OutputCollectionErrorType>[]} errors */
(errors) => {
const firstError = errors.filter((err) => err.type !== 'SOME_FILES_HAS_ERRORS')[0];
if (!firstError) {
this.set$({
commonErrorMessage: null,
});
return;
}
this.set$({
commonErrorMessage: firstError.message,
});
},
);
}
destroyCallback() {
super.destroyCallback();
this.uploadCollection.unobserveProperties(this._throttledHandleCollectionUpdate);
}
}
UploadList.template = /* HTML */ `
<uc-activity-header>
<span aria-live="polite" class="uc-header-text">{{headerText}}</span>
<button
type="button"
class="uc-mini-btn uc-close-btn"
set="onclick: *closeModal"
l10n="@title:a11y-activity-header-button-close;@aria-label:a11y-activity-header-button-close"
>
<uc-icon name="close"></uc-icon>
</button>
</uc-activity-header>
<div class="uc-no-files" set="@hidden: hasFiles">
<slot name="empty"><span l10n="no-files"></span></slot>
</div>
<div class="uc-files">
<div class="uc-files-wrapper" repeat="*uploadList" repeat-item-tag="uc-file-item"></div>
<button
type="button"
class="uc-add-more-btn uc-secondary-btn"
set="onclick: onAdd; @disabled: !addMoreBtnEnabled; @hidden: !addMoreBtnVisible"
>
<uc-icon name="add"></uc-icon><span l10n="add-more"></span>
</button>
</div>
<div class="uc-common-error" set="@hidden: !commonErrorMessage; textContent: commonErrorMessage;"></div>
<div class="uc-toolbar">
<button type="button" class="uc-cancel-btn uc-secondary-btn" set="onclick: onCancel;" l10n="clear"></button>
<div class="uc-toolbar-spacer"></div>
<button
type="button"
class="uc-add-more-btn uc-secondary-btn"
set="onclick: onAdd; @disabled: !addMoreBtnEnabled; @hidden: !addMoreBtnVisible"
>
<uc-icon name="add"></uc-icon><span l10n="add-more"></span>
</button>
<button
type="button"
class="uc-upload-btn uc-primary-btn"
set="@hidden: !uploadBtnVisible; onclick: onUpload;"
l10n="upload"
></button>
<button
type="button"
class="uc-done-btn uc-primary-btn"
set="@hidden: !doneBtnVisible; onclick: onDone; @disabled: !doneBtnEnabled"
l10n="done"
></button>
</div>
<uc-drop-area ghost></uc-drop-area>
`;