UNPKG

@uploadcare/file-uploader

Version:

Building blocks for Uploadcare products integration

293 lines (259 loc) 8.42 kB
// @ts-check import { Data } from '@symbiotejs/symbiote'; import { ActivityBlock } from '../../abstract/ActivityBlock.js'; import { UploaderBlock } from '../../abstract/UploaderBlock.js'; import { stringToArray } from '../../utils/stringToArray.js'; import { asBoolean } from '../Config/validatorsType.js'; import { UploadSource } from '../utils/UploadSource.js'; import { DropzoneState, addDropzone } from './addDropzone.js'; const GLOBAL_CTX_NAME = 'uc-drop-area'; const REGISTRY_KEY = `${GLOBAL_CTX_NAME}/registry`; export class DropArea extends UploaderBlock { static styleAttrs = [...super.styleAttrs, 'uc-drop-area']; constructor() { super(); this.init$ = { ...this.init$, state: DropzoneState.INACTIVE, withIcon: false, isClickable: false, isFullscreen: false, isEnabled: true, isVisible: true, isInitFlow: false, text: '', [REGISTRY_KEY]: null, }; } isActive() { if (!this.$.isEnabled) { return false; } const bounds = this.getBoundingClientRect(); const hasSize = bounds.width > 0 && bounds.height > 0; const isInViewport = bounds.top >= 0 && bounds.left >= 0 && bounds.bottom <= (window.innerHeight || document.documentElement.clientHeight) && bounds.right <= (window.innerWidth || document.documentElement.clientWidth); const style = window.getComputedStyle(this); const visible = style.visibility !== 'hidden' && style.display !== 'none'; return hasSize && visible && isInViewport; } initCallback() { super.initCallback(); this.bindL10n('text', () => this.l10n('drop-files-here')); if (!this.$[REGISTRY_KEY]) { this.$[REGISTRY_KEY] = new Set(); } this.$[REGISTRY_KEY].add(this); this.defineAccessor( 'disabled', /** @param {unknown} value */ (value) => { this.set$({ isEnabled: !asBoolean(value) }); }, ); this.defineAccessor( 'clickable', /** @param {unknown} value */ (value) => { this.set$({ isClickable: asBoolean(value) }); }, ); this.defineAccessor( 'initflow', /** @param {unknown} value */ (value) => { this.set$({ isInitFlow: asBoolean(value) }); }, ); this.defineAccessor( 'with-icon', /** @param {unknown} value */ (value) => { this.set$({ withIcon: asBoolean(value) }); }, ); this.defineAccessor( 'fullscreen', /** @param {unknown} value */ (value) => { this.set$({ isFullscreen: asBoolean(value) }); }, ); this.defineAccessor( 'text', /** @param {unknown} value */ (value) => { if (typeof value === 'string') { this.bindL10n('text', () => this.l10n(value) || value); } else { this.bindL10n('text', () => this.l10n('drop-files-here')); } }, ); /** @private */ this._destroyDropzone = addDropzone({ element: this, shouldIgnore: () => this._shouldIgnore(), /** @param {DropzoneState} state */ onChange: (state) => { this.$.state = state; }, /** @param {import('./getDropItems.js').DropItem[]} items */ onItems: (items) => { if (!items.length) { return; } items.forEach((/** @type {import('./getDropItems.js').DropItem} */ item) => { if (item.type === 'url') { this.api.addFileFromUrl(item.url, { source: UploadSource.DROP_AREA }); } else if (item.type === 'file') { this.api.addFileFromObject(item.file, { source: UploadSource.DROP_AREA, fullPath: item.fullPath }); } }); if (this.uploadCollection.size) { this.modalManager.open(ActivityBlock.activities.UPLOAD_LIST); this.set$({ '*currentActivity': ActivityBlock.activities.UPLOAD_LIST, }); } }, }); let contentWrapperEl = this.ref['content-wrapper']; if (contentWrapperEl) { this._destroyContentWrapperDropzone = addDropzone({ element: contentWrapperEl, /** @param {DropzoneState} state */ onChange: (state) => { const stateText = Object.entries(DropzoneState) .find(([, value]) => value === state)?.[0] .toLowerCase(); stateText && contentWrapperEl.setAttribute('drag-state', stateText); }, onItems: () => {}, shouldIgnore: () => this._shouldIgnore(), }); } this.sub('state', (state) => { const stateText = Object.entries(DropzoneState) .find(([, value]) => value === state)?.[0] .toLowerCase(); if (stateText) { this.setAttribute('drag-state', stateText); } }); this.subConfigValue('sourceList', (value) => { const list = stringToArray(value); // Enable drop area if local files are allowed 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]'); }); this.sub('isVisible', (value) => { this.toggleAttribute('hidden', !value); }); this.sub('isClickable', (value) => { this.toggleAttribute('clickable', value); }); if (this.$.isClickable) { /** * @private * @param {KeyboardEvent | Event} event */ this._onAreaClicked = (event) => { if (event.type === 'keydown') { // @ts-ignore if (event.code === 'Space' || event.code === 'Enter') { if (this.$.isInitFlow) { this.api.initFlow(); return; } this.api.openSystemDialog(); } } else if (event.type === 'click') { if (this.$.isInitFlow) { this.api.initFlow(); return; } this.api.openSystemDialog(); } }; this.addEventListener('keydown', this._onAreaClicked); this.addEventListener('click', this._onAreaClicked); } } /** * Ignore drop events if there are other visible drop areas on the page * * @private * @returns {Boolean} */ _shouldIgnore() { if (!this.$.isEnabled) { return true; } if (!this._couldHandleFiles()) { return true; } if (!this.$.isFullscreen) { return false; } const otherTargets = [...this.$[REGISTRY_KEY]].filter((el) => el !== this); const activeTargets = otherTargets.filter((/** @type {typeof this} */ el) => { return el.isActive(); }); return activeTargets.length > 0; } /** @private */ _couldHandleFiles() { let isMultiple = this.cfg.multiple; let multipleMax = this.cfg.multipleMax; let currentFilesCount = this.uploadCollection.size; if (isMultiple && multipleMax && currentFilesCount >= multipleMax) { return false; } if (!isMultiple && currentFilesCount > 0) { return false; } return true; } destroyCallback() { super.destroyCallback(); /** @type {Set<DropArea>} */ const registry = this.$[REGISTRY_KEY]; if (registry) { registry.delete(this); if (registry.size === 0) { Data.deleteCtx(GLOBAL_CTX_NAME); } } this._destroyDropzone?.(); this._destroyContentWrapperDropzone?.(); if (this._onAreaClicked) { this.removeEventListener('keydown', this._onAreaClicked); this.removeEventListener('click', this._onAreaClicked); } } } DropArea.template = /* HTML */ ` <slot> <div data-default-slot hidden></div> <div ref="content-wrapper" class="uc-content-wrapper" set="@hidden: !isVisible"> <div class="uc-icon-container" set="@hidden: !withIcon"> <uc-icon name="default"></uc-icon> <uc-icon name="arrow-down"></uc-icon> </div> <span class="uc-text">{{text}}</span> </div> </slot> `; DropArea.bindAttributes({ // @ts-expect-error TODO: fix types inside symbiote 'with-icon': null, // @ts-expect-error TODO: fix types inside symbiote clickable: null, // @ts-expect-error TODO: fix types inside symbiote text: null, // @ts-expect-error TODO: fix types inside symbiote fullscreen: null, // @ts-expect-error TODO: fix types inside symbiote disabled: null, // @ts-expect-error TODO: fix types inside symbiote initflow: null, });