diff --git a/abstract/Block.js b/abstract/Block.js index b0c5d57d9..995c828e0 100644 --- a/abstract/Block.js +++ b/abstract/Block.js @@ -1,5 +1,5 @@ // @ts-check -import { BaseComponent } from '@symbiotejs/symbiote'; +import { BaseComponent, Data } from '@symbiotejs/symbiote'; import { EventEmitter } from '../blocks/UploadCtxProvider/EventEmitter.js'; import { createWindowHeightTracker, getIsWindowHeightTracked } from '../utils/createWindowHeightTracker.js'; import { getPluralForm } from '../utils/getPluralForm.js'; @@ -188,8 +188,18 @@ export class Block extends BaseComponent { } destroyCallback() { + /** @type {Set} */ let blocksRegistry = this.$['*blocksRegistry']; blocksRegistry.delete(this); + + // Destroy local context + // TODO: this should be done inside symbiote + Data.deleteCtx(this); + + if (blocksRegistry.size === 0) { + // Destroy external context if there is no any blocks left inside it + Data.deleteCtx(this.ctxName); + } } /** diff --git a/abstract/TypedCollection.js b/abstract/TypedCollection.js index bf9f58812..986deb39f 100644 --- a/abstract/TypedCollection.js +++ b/abstract/TypedCollection.js @@ -117,7 +117,7 @@ export class TypedCollection { /** @param {Function} handler */ unobserveCollection(handler) { - this.__collectionObservers.delete(handler); + this.__collectionObservers?.delete(handler); } /** @@ -201,7 +201,7 @@ export class TypedCollection { /** @param {Function} handler */ unobserveProperties(handler) { - this.__propertyObservers.delete(handler); + this.__propertyObservers?.delete(handler); } /** @@ -228,7 +228,7 @@ export class TypedCollection { } destroy() { - Data.deleteCtx(this.__data); + Data.deleteCtx(this.__ctxId); this.__propertyObservers = null; this.__collectionObservers = null; for (let id in this.__subsMap) { diff --git a/abstract/UploaderBlock.js b/abstract/UploaderBlock.js index 91e11eacb..3b181a0bc 100644 --- a/abstract/UploaderBlock.js +++ b/abstract/UploaderBlock.js @@ -19,6 +19,7 @@ import { TypedCollection } from './TypedCollection.js'; import { uploadEntrySchema } from './uploadEntrySchema.js'; export class UploaderBlock extends ActivityBlock { + couldBeCtxOwner = false; isCtxOwner = false; init$ = uploaderBlockCtx(this); @@ -84,7 +85,7 @@ export class UploaderBlock extends ActivityBlock { this.add('*uploadCollection', uploadCollection); } - if (!this.hasCtxOwner) { + if (!this.hasCtxOwner && this.couldBeCtxOwner) { this.initCtxOwner(); } } @@ -94,6 +95,7 @@ export class UploaderBlock extends ActivityBlock { if (this.isCtxOwner) { this._unobserveCollectionProperties?.(); this._unobserveCollection?.(); + this.uploadCollection.destroy(); } } diff --git a/blocks/CameraSource/CameraSource.js b/blocks/CameraSource/CameraSource.js index a72a323f7..c5a241382 100644 --- a/blocks/CameraSource/CameraSource.js +++ b/blocks/CameraSource/CameraSource.js @@ -5,6 +5,7 @@ import { debounce } from '../utils/debounce.js'; import { UploadSource } from '../utils/UploadSource.js'; export class CameraSource extends UploaderBlock { + couldBeCtxOwner = true; activityType = ActivityBlock.activities.CAMERA; /** @private */ diff --git a/blocks/CloudImageEditor/src/CloudImageEditorBlock.js b/blocks/CloudImageEditor/src/CloudImageEditorBlock.js index 9c094c300..349bc9764 100644 --- a/blocks/CloudImageEditor/src/CloudImageEditorBlock.js +++ b/blocks/CloudImageEditor/src/CloudImageEditorBlock.js @@ -11,8 +11,8 @@ import { debounce } from '../../utils/debounce.js'; import { CloudImageEditorBase } from './CloudImageEditorBase.js'; import { classNames } from './lib/classNames.js'; import { parseCropPreset } from './lib/parseCropPreset.js'; -import { operationsToTransformations, transformationsToOperations } from './lib/transformationUtils.js'; import { parseTabs } from './lib/parseTabs.js'; +import { operationsToTransformations, transformationsToOperations } from './lib/transformationUtils.js'; import { initState } from './state.js'; import { TEMPLATE } from './template.js'; import { TabId } from './toolbar-constants.js'; diff --git a/blocks/CloudImageEditorActivity/CloudImageEditorActivity.js b/blocks/CloudImageEditorActivity/CloudImageEditorActivity.js index 1a23093b0..2b495e4d7 100644 --- a/blocks/CloudImageEditorActivity/CloudImageEditorActivity.js +++ b/blocks/CloudImageEditorActivity/CloudImageEditorActivity.js @@ -4,6 +4,7 @@ import { UploaderBlock } from '../../abstract/UploaderBlock.js'; import { CloudImageEditorBlock } from '../CloudImageEditor/index.js'; export class CloudImageEditorActivity extends UploaderBlock { + couldBeCtxOwner = true; activityType = ActivityBlock.activities.CLOUD_IMG_EDIT; constructor() { diff --git a/blocks/Config/Config.js b/blocks/Config/Config.js index 6f6f6e851..3fbd9ceff 100644 --- a/blocks/Config/Config.js +++ b/blocks/Config/Config.js @@ -39,7 +39,6 @@ const attrStateMapping = /** @type {Record el !== this); + const otherTargets = [...this.$[REGISTRY_KEY]].filter((el) => el !== this); const activeTargets = otherTargets.filter((/** @type {typeof this} */ el) => { return el.isActive(); }); @@ -209,7 +213,14 @@ export class DropArea extends UploaderBlock { destroyCallback() { super.destroyCallback(); - this.$['lr-drop-area/targets']?.remove?.(this); + /** @type {Set} */ + const registry = this.$[REGISTRY_KEY]; + if (registry) { + registry.delete(this); + if (registry.size === 0) { + Data.deleteCtx(GLOBAL_CTX_NAME); + } + } this._destroyDropzone?.(); this._destroyContentWrapperDropzone?.(); diff --git a/blocks/ExternalSource/ExternalSource.js b/blocks/ExternalSource/ExternalSource.js index 56a55de9f..70ca3e188 100644 --- a/blocks/ExternalSource/ExternalSource.js +++ b/blocks/ExternalSource/ExternalSource.js @@ -31,6 +31,7 @@ import { queryString } from './query-string.js'; /** @typedef {SelectedFileMessage | EmbedCssMessage} Message */ export class ExternalSource extends UploaderBlock { + couldBeCtxOwner = true; activityType = ActivityBlock.activities.EXTERNAL; constructor() { diff --git a/blocks/FileItem/FileItem.js b/blocks/FileItem/FileItem.js index 0143afa79..1b48507c8 100644 --- a/blocks/FileItem/FileItem.js +++ b/blocks/FileItem/FileItem.js @@ -17,6 +17,7 @@ const FileItemState = Object.freeze({ }); export class FileItem extends UploaderBlock { + couldBeCtxOwner = true; pauseRender = true; /** @private */ diff --git a/blocks/Img/ImgBase.js b/blocks/Img/ImgBase.js index cd75a762d..9343517da 100644 --- a/blocks/Img/ImgBase.js +++ b/blocks/Img/ImgBase.js @@ -1,4 +1,4 @@ -import { BaseComponent } from '@symbiotejs/symbiote'; +import { BaseComponent, Data } from '@symbiotejs/symbiote'; import { applyTemplateData } from '../../utils/template-utils.js'; import { createCdnUrl, createCdnUrlModifiers, createOriginalUrl } from '../../utils/cdn-utils.js'; import { PROPS_MAP } from './props-map.js'; @@ -339,6 +339,7 @@ export class ImgBase extends BaseComponent { }); this._isnObserver = null; } + Data.deleteCtx(this); } static get observedAttributes() { diff --git a/blocks/SimpleBtn/SimpleBtn.js b/blocks/SimpleBtn/SimpleBtn.js index d1f3672c9..877f12f71 100644 --- a/blocks/SimpleBtn/SimpleBtn.js +++ b/blocks/SimpleBtn/SimpleBtn.js @@ -3,6 +3,7 @@ import { UploaderBlock } from '../../abstract/UploaderBlock.js'; import { asBoolean } from '../Config/normalizeConfigValue.js'; export class SimpleBtn extends UploaderBlock { + couldBeCtxOwner = true; constructor() { super(); diff --git a/blocks/SourceBtn/SourceBtn.js b/blocks/SourceBtn/SourceBtn.js index ccf1505d3..a4d357efd 100644 --- a/blocks/SourceBtn/SourceBtn.js +++ b/blocks/SourceBtn/SourceBtn.js @@ -4,6 +4,7 @@ import { ActivityBlock } from '../../abstract/ActivityBlock.js'; const L10N_PREFIX = 'src-type-'; export class SourceBtn extends UploaderBlock { + couldBeCtxOwner = true; /** @private */ _registeredTypes = {}; diff --git a/blocks/UploadCtxProvider/EventEmitter.js b/blocks/UploadCtxProvider/EventEmitter.js index 586dfd0d7..716a73d4c 100644 --- a/blocks/UploadCtxProvider/EventEmitter.js +++ b/blocks/UploadCtxProvider/EventEmitter.js @@ -61,6 +61,9 @@ export class EventEmitter { */ _timeoutStore = new Map(); + /** @type {Set} */ + _targets = new Set(); + /** @param {() => string} getCtxName */ constructor(getCtxName) { /** @private */ @@ -73,8 +76,12 @@ export class EventEmitter { /** @param {import('../../abstract/Block.js').Block} target */ bindTarget(target) { - /** @private */ - this._target = target; + this._targets.add(target); + } + + /** @param {import('../../abstract/Block.js').Block} target */ + unbindTarget(target) { + this._targets.delete(target); } /** @@ -84,11 +91,13 @@ export class EventEmitter { * @param {EventPayload[T]} [payload] */ _dispatch(type, payload) { - this._target?.dispatchEvent( - new CustomEvent(type, { - detail: payload, - }) - ); + for (const target of this._targets) { + target.dispatchEvent( + new CustomEvent(type, { + detail: payload, + }) + ); + } const globalEventType = GlobalEventType[type]; window.dispatchEvent( diff --git a/blocks/UploadCtxProvider/UploadCtxProvider.js b/blocks/UploadCtxProvider/UploadCtxProvider.js index 4f73364f8..f50d5233f 100644 --- a/blocks/UploadCtxProvider/UploadCtxProvider.js +++ b/blocks/UploadCtxProvider/UploadCtxProvider.js @@ -9,6 +9,12 @@ class UploadCtxProviderClass extends UploaderBlock { this.$['*eventEmitter'].bindTarget(this); } + + destroyCallback() { + super.destroyCallback(); + + this.$['*eventEmitter'].unbindTarget(this); + } } /** diff --git a/blocks/UploadList/UploadList.js b/blocks/UploadList/UploadList.js index 9195771ab..9c20b5745 100644 --- a/blocks/UploadList/UploadList.js +++ b/blocks/UploadList/UploadList.js @@ -16,6 +16,9 @@ import { debounce } from '../utils/debounce.js'; */ 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; diff --git a/blocks/UrlSource/UrlSource.js b/blocks/UrlSource/UrlSource.js index 31270113e..22a30feac 100644 --- a/blocks/UrlSource/UrlSource.js +++ b/blocks/UrlSource/UrlSource.js @@ -3,6 +3,7 @@ import { ActivityBlock } from '../../abstract/ActivityBlock.js'; import { UploadSource } from '../utils/UploadSource.js'; export class UrlSource extends UploaderBlock { + couldBeCtxOwner = true; activityType = ActivityBlock.activities.URL; init$ = {