diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts index a0deb3801e19..cb514a6e5075 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts @@ -15,7 +15,7 @@ import { UmbArrayState, UmbBasicState, UmbNumberState, UmbObjectState } from '@u import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api'; -import { UmbSelectionManager, UmbPaginationManager, UmbDeprecation } from '@umbraco-cms/backoffice/utils'; +import { UmbSelectionManager, UmbPaginationManager, UmbDeprecation, debounce } from '@umbraco-cms/backoffice/utils'; import type { ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @@ -121,11 +121,11 @@ export class UmbDefaultCollectionContext< }) .onReject(() => { // TODO: Maybe this can be removed? - this.requestCollection(); + this._requestCollection(); }) .onSubmit(() => { // TODO: Maybe this can be removed? - this.requestCollection(); + this._requestCollection(); }) .observeRouteBuilder((routeBuilder) => { this.#workspacePathBuilder.setValue(routeBuilder); @@ -248,16 +248,30 @@ export class UmbDefaultCollectionContext< return this.manifest?.meta.noItemsLabel ?? this.#config?.noItemsLabel ?? '#collection_noItemsTitle'; } + /* debouncing the load collection method because multiple filters can be set at the same time + that will trigger multiple load calls with different filter arguments */ + public loadCollection = debounce(() => this._requestCollection(), 100); + /** * Requests the collection from the repository. - * @returns {*} + * @returns {Promise} + * @deprecated Deprecated since v.17.0.0. Use `loadCollection` instead. * @memberof UmbCollectionContext */ public async requestCollection() { + new UmbDeprecation({ + removeInVersion: '19.0.0', + deprecated: 'requestCollection', + solution: 'Use .loadCollection method instead', + }).warn(); + + return this._requestCollection(); + } + + protected async _requestCollection() { await this._init; if (!this._configured) this._configure(); - if (!this._repository) throw new Error(`Missing repository for ${this._manifest}`); this._loading.setValue(true); @@ -281,7 +295,7 @@ export class UmbDefaultCollectionContext< */ public setFilter(filter: Partial) { this._filter.setValue({ ...this._filter.getValue(), ...filter }); - this.requestCollection(); + this.loadCollection(); } public updateFilter(filter: Partial) { @@ -312,7 +326,7 @@ export class UmbDefaultCollectionContext< const items = this._items.getValue(); const hasItem = items.some((item) => item.unique === event.getUnique()); if (hasItem) { - this.requestCollection(); + this._requestCollection(); } }; @@ -324,7 +338,7 @@ export class UmbDefaultCollectionContext< const entityType = entityContext.getEntityType(); if (unique === event.getUnique() && entityType === event.getEntityType()) { - this.requestCollection(); + this._requestCollection(); } }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts index 94440fa140a4..1da4b1a59a9c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts @@ -23,7 +23,6 @@ umbExtensionsRegistry.register(manifest); @customElement('umb-collection-default') export class UmbCollectionDefaultElement extends UmbLitElement { - // #collectionContext?: UmbDefaultCollectionContext; @state() @@ -33,23 +32,43 @@ export class UmbCollectionDefaultElement extends UmbLitElement { private _hasItems = false; @state() - private _isDoneLoading = false; + private _emptyLabel?: string; @state() - private _emptyLabel?: string; + private _initialLoadDone = false; constructor() { super(); this.consumeContext(UMB_COLLECTION_CONTEXT, async (context) => { this.#collectionContext = context; + this.#observeIsLoading(); this.#observeCollectionRoutes(); this.#observeTotalItems(); this.#getEmptyStateLabel(); - await this.#collectionContext?.requestCollection(); - this._isDoneLoading = true; + this.#collectionContext?.loadCollection(); }); } + #observeIsLoading() { + if (!this.#collectionContext) return; + let hasBeenLoading = false; + + this.observe( + this.#collectionContext.loading, + (isLoading) => { + // We need to know when the initial loading has been done, to not show the empty state before that. + // We can't just check if there are items, because there might be none. + // So we check if it has been loading, and then when it stops loading we know the initial load is done. + if (isLoading) { + hasBeenLoading = true; + } else if (hasBeenLoading) { + this._initialLoadDone = true; + } + }, + 'umbCollectionIsLoadingObserver', + ); + } + #observeCollectionRoutes() { if (!this.#collectionContext) return; @@ -106,7 +125,7 @@ export class UmbCollectionDefaultElement extends UmbLitElement { } #renderEmptyState() { - if (!this._isDoneLoading) return nothing; + if (!this._initialLoadDone) return nothing; return html`
@@ -138,12 +157,20 @@ export class UmbCollectionDefaultElement extends UmbLitElement { height: 80%; align-content: center; text-align: center; + opacity: 0; + animation: fadeIn 200ms 200ms forwards; } router-slot { width: 100%; height: 100%; } + + @keyframes fadeIn { + 100% { + opacity: 100%; + } + } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/document-collection.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/document-collection.context.ts index cff1aedd6589..18c8a5bcf854 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/document-collection.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/document-collection.context.ts @@ -4,6 +4,7 @@ import { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection' import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UMB_VARIANT_CONTEXT } from '@umbraco-cms/backoffice/variant'; import { UmbStringState } from '@umbraco-cms/backoffice/observable-api'; +import { UmbDeprecation } from '@umbraco-cms/backoffice/utils'; export class UmbDocumentCollectionContext extends UmbDefaultCollectionContext< UmbDocumentCollectionItemModel, @@ -35,9 +36,25 @@ export class UmbDocumentCollectionContext extends UmbDefaultCollectionContext< ); } - public override async requestCollection() { + /** + * Requests the collection from the repository. + * @returns {Promise} + * @deprecated Deprecated since v.17.0.0. Use `loadCollection` instead. + * @memberof UmbDocumentCollectionContext + */ + public override async requestCollection(): Promise { + new UmbDeprecation({ + removeInVersion: '19.0.0', + deprecated: 'requestCollection', + solution: 'Use .loadCollection method instead', + }).warn(); + + return this._requestCollection(); + } + + protected override async _requestCollection() { await this.observe(this.#displayCultureObservable)?.asPromise(); - await super.requestCollection(); + await super._requestCollection(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts index 66849f58ff04..08632022edee 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts @@ -5,6 +5,7 @@ import type { UmbFileDropzoneItemStatus } from '@umbraco-cms/backoffice/dropzone import { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; +import { UmbDeprecation } from '@umbraco-cms/backoffice/utils'; export class UmbMediaCollectionContext extends UmbDefaultCollectionContext< UmbMediaCollectionItemModel, UmbMediaCollectionFilterModel @@ -51,9 +52,20 @@ export class UmbMediaCollectionContext extends UmbDefaultCollectionContext< /** * Requests the collection from the repository. * @returns {Promise} - * @memberof UmbCollectionContext + * @deprecated Deprecated since v.17.0.0. Use `loadCollection` instead. + * @memberof UmbMediaCollectionContext */ - public override async requestCollection() { + public override async requestCollection(): Promise { + new UmbDeprecation({ + removeInVersion: '19.0.0', + deprecated: 'requestCollection', + solution: 'Use .loadCollection method instead', + }).warn(); + + return this._requestCollection(); + } + + protected override async _requestCollection() { await this._init; if (!this._configured) this._configure(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.element.ts index 2de841ae73b4..a59d2b4c5b6d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.element.ts @@ -62,7 +62,6 @@ export class UmbMediaCollectionElement extends UmbCollectionDefaultElement { async #onComplete(event: Event) { event.preventDefault(); this._progress = -1; - this.#collectionContext?.requestCollection(); const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); if (!eventContext) {