UNPKG

@uploadcare/file-uploader

Version:

Building blocks for Uploadcare products integration

299 lines (260 loc) 8.68 kB
// @ts-check import { create } from '@symbiotejs/symbiote'; import { ActivityBlock } from '../../abstract/ActivityBlock.js'; import { UploaderBlock } from '../../abstract/UploaderBlock.js'; import { stringToArray } from '../../utils/stringToArray.js'; import { wildcardRegexp } from '../../utils/wildcardRegexp.js'; import { buildThemeDefinition } from './buildThemeDefinition.js'; import { MessageBridge } from './MessageBridge.js'; import { queryString } from './query-string.js'; /** @typedef {{ externalSourceType: string }} ActivityParams */ export class ExternalSource extends UploaderBlock { couldBeCtxOwner = true; activityType = ActivityBlock.activities.EXTERNAL; constructor() { super(); this.init$ = { ...this.init$, activityIcon: '', activityCaption: '', /** @type {import('./types.js').InputMessageMap['selected-files-change']['selectedFiles']} */ selectedList: [], total: 0, isSelectionReady: false, isDoneBtnEnabled: false, couldSelectAll: false, couldDeselectAll: false, showSelectionStatus: false, counterText: '', doneBtnTextClass: 'uc-hidden', onDone: () => { for (const message of this.$.selectedList) { const url = this.extractUrlFromSelectedFile(message); const { filename } = message; const { externalSourceType } = this.activityParams; this.api.addFileFromUrl(url, { fileName: filename, source: externalSourceType }); } this.$['*currentActivity'] = ActivityBlock.activities.UPLOAD_LIST; this.modalManager.open(ActivityBlock.activities.UPLOAD_LIST); }, onCancel: () => { this.historyBack(); }, onSelectAll: () => { this._messageBridge?.send({ type: 'select-all' }); }, onDeselectAll: () => { this._messageBridge?.send({ type: 'deselect-all' }); }, }; } /** @type {ActivityParams} */ get activityParams() { const params = super.activityParams; if ('externalSourceType' in params) { return params; } throw new Error(`External Source activity params not found`); } initCallback() { super.initCallback(); this.registerActivity(this.activityType, { onActivate: () => { let { externalSourceType } = /** @type {ActivityParams} */ (this.activityParams); if (!externalSourceType) { this.modalManager.close(this.$['*currentActivity']); this.$['*currentActivity'] = null; console.error(`Param "externalSourceType" is required for activity "${this.activityType}"`); return; } this.set$({ activityCaption: `${externalSourceType?.[0].toUpperCase()}${externalSourceType?.slice(1)}`, activityIcon: externalSourceType, }); this.mountIframe(); }, }); this.sub('*currentActivityParams', (val) => { if (!this.isActivityActive) { return; } this.unmountIframe(); this.mountIframe(); }); this.sub('*currentActivity', (val) => { if (val !== this.activityType) { this.unmountIframe(); } }); this.subConfigValue('multiple', (multiple) => { this.$.showSelectionStatus = multiple; }); this.subConfigValue('localeName', (val) => { this.setupL10n(); }); this.subConfigValue('externalSourcesEmbedCss', (embedCss) => { this.applyEmbedCss(embedCss); }); } /** * @private * @param {NonNullable<import('./types.js').InputMessageMap['selected-files-change']['selectedFiles']>[number]} selectedFile */ extractUrlFromSelectedFile(selectedFile) { if (selectedFile.alternatives) { const preferredTypes = stringToArray(this.cfg.externalSourcesPreferredTypes); for (const preferredType of preferredTypes) { const regexp = wildcardRegexp(preferredType); for (const [type, typeUrl] of Object.entries(selectedFile.alternatives)) { if (regexp.test(type)) { return typeUrl; } } } } return selectedFile.url; } /** * @private * @param {import('./types.js').InputMessageMap['selected-files-change']} message */ async handleSelectedFilesChange(message) { if (this.cfg.multiple !== message.isMultipleMode) { console.error('Multiple mode mismatch'); return; } this.bindL10n('counterText', () => this.l10n('selected-count', { count: message.selectedCount, total: message.total, }), ); this.set$({ doneBtnTextClass: message.isReady ? '' : 'uc-hidden', isSelectionReady: message.isReady, isDoneBtnEnabled: message.isReady && message.selectedFiles.length > 0, showSelectionStatus: message.isMultipleMode && message.total > 0, couldSelectAll: message.selectedCount < message.total, couldDeselectAll: message.selectedCount === message.total, selectedList: message.selectedFiles, }); } /** @private */ handleIframeLoad() { this.applyEmbedCss(this.cfg.externalSourcesEmbedCss); this.applyTheme(); this.setupL10n(); } /** @private */ applyTheme() { this._messageBridge?.send({ type: 'set-theme-definition', theme: buildThemeDefinition(this), }); } /** * @private * @param {string} css */ applyEmbedCss(css) { this._messageBridge?.send({ type: 'set-embed-css', css, }); } /** @private */ setupL10n() { this._messageBridge?.send({ type: 'set-locale-definition', localeDefinition: this.cfg.localeName, }); } /** @private */ remoteUrl() { const { pubkey, remoteTabSessionKey, socialBaseUrl, multiple } = this.cfg; const { externalSourceType } = this.activityParams; const lang = this.l10n('social-source-lang')?.split('-')?.[0] || 'en'; const params = { lang, public_key: pubkey, images_only: false.toString(), session_key: remoteTabSessionKey, wait_for_theme: true, multiple: multiple.toString(), }; const url = new URL(`/window4/${externalSourceType}`, socialBaseUrl); url.search = queryString(params); return url.toString(); } /** @private */ mountIframe() { /** @type {HTMLIFrameElement} */ // @ts-ignore let iframe = create({ tag: 'iframe', attributes: { src: this.remoteUrl(), marginheight: 0, marginwidth: 0, frameborder: 0, allowTransparency: true, }, }); iframe.addEventListener('load', this.handleIframeLoad.bind(this)); this.ref.iframeWrapper.innerHTML = ''; this.ref.iframeWrapper.appendChild(iframe); if (!iframe.contentWindow) { return; } this._messageBridge?.destroy(); /** @private */ this._messageBridge = new MessageBridge(iframe.contentWindow); this._messageBridge.on('selected-files-change', this.handleSelectedFilesChange.bind(this)); this.resetSelectionStatus(); } /** @private */ unmountIframe() { this._messageBridge?.destroy(); this._messageBridge = undefined; this.ref.iframeWrapper.innerHTML = ''; this.resetSelectionStatus(); } /** @private */ resetSelectionStatus() { this.set$({ selectedList: [], total: 0, isDoneBtnEnabled: false, couldSelectAll: false, couldDeselectAll: false, showSelectionStatus: false, }); } } ExternalSource.template = /* HTML */ ` <uc-activity-header> <button type="button" class="uc-mini-btn uc-close-btn" set="onclick: *historyBack" 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-content"> <div ref="iframeWrapper" class="uc-iframe-wrapper"></div> <div class="uc-toolbar"> <button type="button" class="uc-cancel-btn uc-secondary-btn" set="onclick: onCancel" l10n="cancel"></button> <div set="@hidden: !showSelectionStatus" class="uc-selection-status-box"> <span>{{counterText}}</span> <button type="button" set="onclick: onSelectAll; @hidden: !couldSelectAll" l10n="select-all"></button> <button type="button" set="onclick: onDeselectAll; @hidden: !couldDeselectAll" l10n="deselect-all"></button> </div> <button type="button" class="uc-done-btn uc-primary-btn" set="onclick: onDone; @disabled: !isDoneBtnEnabled;"> <uc-spinner set="@hidden: isSelectionReady"></uc-spinner> <span l10n="done" set="@class: doneBtnTextClass"></span> </button> </div> </div> `;