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 4a2df0b6c3..76f087c353 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 @@ -31,6 +31,9 @@ export class UmbDefaultCollectionContext< #manifest?: ManifestCollection; #repository?: UmbCollectionRepository; + #loading = new UmbObjectState(false); + public readonly loading = this.#loading.asObservable(); + #items = new UmbArrayState([], (x) => x); public readonly items = this.#items.asObservable(); @@ -176,6 +179,8 @@ export class UmbDefaultCollectionContext< if (!this.#repository) throw new Error(`Missing repository for ${this.#manifest}`); + this.#loading.setValue(true); + const filter = this.#filter.getValue(); const { data } = await this.#repository.requestCollection(filter); @@ -184,6 +189,8 @@ export class UmbDefaultCollectionContext< this.#totalItems.setValue(data.total); this.pagination.setTotalItems(data.total); } + + this.#loading.setValue(false); } /** 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 49fa7629b3..6a60bd56aa 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 @@ -57,7 +57,7 @@ export class UmbCollectionDefaultElement extends UmbLitElement { return html` ${this.renderToolbar()} - + ${this.renderPagination()} ${this.renderSelectionActions()} `; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/types.ts index ffcbcb13d3..c3a7b16517 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/types.ts @@ -6,7 +6,7 @@ export interface UmbDocumentCollectionFilterModel extends UmbCollectionFilterMod orderBy?: string; orderCulture?: string; orderDirection?: 'asc' | 'desc'; - userDefinedProperties: Array<{alias: string, header: string, isSystem: boolean}>; + userDefinedProperties: Array<{ alias: string; header: string; isSystem: boolean }>; } export interface UmbDocumentCollectionItemModel { @@ -23,3 +23,8 @@ export interface UmbDocumentCollectionItemModel { updater?: string | null; values: Array<{ alias: string; value: string }>; } + +export interface UmbEditableDocumentCollectionItemModel { + item: UmbDocumentCollectionItemModel; + editPath: string; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/grid/document-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/grid/document-grid-collection-view.element.ts index 673c99e34c..0cc2874dea 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/grid/document-grid-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/grid/document-grid-collection-view.element.ts @@ -1,15 +1,21 @@ import { getPropertyValueByAlias } from '../index.js'; +import { UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN } from '../../../paths.js'; import type { UmbCollectionColumnConfiguration } from '../../../../../core/collection/types.js'; import type { UmbDocumentCollectionFilterModel, UmbDocumentCollectionItemModel } from '../../types.js'; -import { css, html, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { css, customElement, html, nothing, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; import { fromCamelCase } from '@umbraco-cms/backoffice/utils'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection'; +import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal'; import type { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection'; +import type { UUIInterfaceColor } from '@umbraco-cms/backoffice/external/uui'; @customElement('umb-document-grid-collection-view') export class UmbDocumentGridCollectionViewElement extends UmbLitElement { + @state() + private _editDocumentPath = ''; + @state() private _items: Array = []; @@ -19,9 +25,6 @@ export class UmbDocumentGridCollectionViewElement extends UmbLitElement { @state() private _selection: Array = []; - @state() - private _skip: number = 0; - @state() private _userDefinedProperties?: Array; @@ -34,40 +37,51 @@ export class UmbDocumentGridCollectionViewElement extends UmbLitElement { this.#collectionContext = collectionContext; this.#observeCollectionContext(); }); + + new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) + .addAdditionalPath('document') + .onSetup(() => { + return { data: { entityType: 'document', preset: {} } }; + }) + .onReject(() => { + this.#collectionContext?.requestCollection(); + }) + .onSubmit(() => { + this.#collectionContext?.requestCollection(); + }) + .observeRouteBuilder((routeBuilder) => { + this._editDocumentPath = routeBuilder({}); + }); } #observeCollectionContext() { if (!this.#collectionContext) return; + this.observe(this.#collectionContext.loading, (loading) => (this._loading = loading), '_observeLoading'); + this.observe( this.#collectionContext.userDefinedProperties, (userDefinedProperties) => { this._userDefinedProperties = userDefinedProperties; }, - 'umbCollectionUserDefinedPropertiesObserver', + '_observeUserDefinedProperties', ); - this.observe(this.#collectionContext.items, (items) => (this._items = items), 'umbCollectionItemsObserver'); + this.observe(this.#collectionContext.items, (items) => (this._items = items), '_observeItems'); this.observe( this.#collectionContext.selection.selection, (selection) => (this._selection = selection), - 'umbCollectionSelectionObserver', - ); - - this.observe( - this.#collectionContext.pagination.skip, - (skip) => { - this._skip = skip; - }, - 'umbCollectionSkipObserver', + '_observeSelection', ); } - // TODO: How should we handle url stuff? [?] - #onOpen(id: string) { - // TODO: this will not be needed when cards works as links with href [?] - history.pushState(null, '', 'section/content/workspace/document/edit/' + id); + #onOpen(event: Event, unique: string) { + event.preventDefault(); + event.stopPropagation(); + + const url = this._editDocumentPath + UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN.generateLocal({ unique }); + window.history.pushState(null, '', url); } #onSelect(item: UmbDocumentCollectionItemModel) { @@ -83,33 +97,44 @@ export class UmbDocumentGridCollectionViewElement extends UmbLitElement { } render() { - if (this._loading) { - return html`
`; - } - - if (this._items.length === 0) { - return html`

${this.localize.term('content_listViewNoItems')}

`; - } + return this._items.length === 0 ? this.#renderEmpty() : this.#renderItems(); + } + #renderEmpty() { + if (this._items.length > 0) return nothing; return html` -
- ${repeat( - this._items, - (item) => item.unique, - (item) => this.#renderCard(item), +
+ ${when( + this._loading, + () => html``, + () => html`

${this.localize.term('content_listViewNoItems')}

`, )}
`; } - #renderCard(item: UmbDocumentCollectionItemModel) { + #renderItems() { + if (this._items.length === 0) return nothing; + return html` +
+ ${repeat( + this._items, + (item) => item.unique, + (item) => this.#renderItem(item), + )} +
+ ${when(this._loading, () => html``)} + `; + } + + #renderItem(item: UmbDocumentCollectionItemModel) { return html` 0} ?selected=${this.#isSelected(item)} - @open=${() => this.#onOpen(item.unique ?? '')} + @open=${(event: Event) => this.#onOpen(event, item.unique)} @selected=${() => this.#onSelect(item)} @deselected=${() => this.#onDeselect(item)}> @@ -118,29 +143,26 @@ export class UmbDocumentGridCollectionViewElement extends UmbLitElement { `; } - #renderState(item: UmbDocumentCollectionItemModel) { - switch (item.state) { + #getStateTagConfig(state: string): { color: UUIInterfaceColor; label: string } { + switch (state) { case 'Published': - return html`${this.localize.term('content_published')}`; + return { color: 'positive', label: this.localize.term('content_published') }; case 'PublishedPendingChanges': - return html`${this.localize.term('content_publishedPendingChanges')}`; + return { color: 'warning', label: this.localize.term('content_publishedPendingChanges') }; case 'Draft': - return html`${this.localize.term('content_unpublished')}`; + return { color: 'default', label: this.localize.term('content_unpublished') }; case 'NotCreated': - return html`${this.localize.term('content_notCreated')}`; + return { color: 'danger', label: this.localize.term('content_notCreated') }; default: - return html`${fromCamelCase(item.state)}`; + return { color: 'danger', label: fromCamelCase(state) }; } } + #renderState(item: UmbDocumentCollectionItemModel) { + const tagConfig = this.#getStateTagConfig(item.state); + return html`${tagConfig.label}`; + } + #renderProperties(item: UmbDocumentCollectionItemModel) { if (!this._userDefinedProperties) return; return html` diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-name.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-name.element.ts index 1d9e39d4b3..0801145f11 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-name.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-name.element.ts @@ -1,49 +1,30 @@ -import type { UmbDocumentCollectionItemModel } from '../../../types.js'; -import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; +import type { UmbEditableDocumentCollectionItemModel } from '../../../types.js'; +import { css, customElement, html, nothing, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal'; import type { UmbTableColumn, UmbTableColumnLayoutElement, UmbTableItem } from '@umbraco-cms/backoffice/components'; import type { UUIButtonElement } from '@umbraco-cms/backoffice/external/uui'; @customElement('umb-document-table-column-name') export class UmbDocumentTableColumnNameElement extends UmbLitElement implements UmbTableColumnLayoutElement { - @state() - private _editDocumentPath = ''; - - @property({ type: Object, attribute: false }) column!: UmbTableColumn; - - @property({ type: Object, attribute: false }) item!: UmbTableItem; @property({ attribute: false }) - value!: UmbDocumentCollectionItemModel; - - constructor() { - super(); - - new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) - .addAdditionalPath('document') - .onSetup(() => { - return { data: { entityType: 'document', preset: {} } }; - }) - .observeRouteBuilder((routeBuilder) => { - this._editDocumentPath = routeBuilder({}); - }); - } + value!: UmbEditableDocumentCollectionItemModel; #onClick(event: Event & { target: UUIButtonElement }) { event.preventDefault(); event.stopPropagation(); - window.history.pushState({}, '', event.target.href); + window.history.pushState(null, '', event.target.href); } render() { + if (!this.value) return nothing; return html` `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-state.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-state.element.ts index 1a44194e82..11c1e6985b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-state.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-state.element.ts @@ -1,4 +1,4 @@ -import type { UmbDocumentCollectionItemModel } from '../../../types.js'; +import type { UmbEditableDocumentCollectionItemModel } from '../../../types.js'; import { customElement, html, property } from '@umbraco-cms/backoffice/external/lit'; import { fromCamelCase } from '@umbraco-cms/backoffice/utils'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -6,17 +6,14 @@ import type { UmbTableColumn, UmbTableColumnLayoutElement, UmbTableItem } from ' @customElement('umb-document-table-column-state') export class UmbDocumentTableColumnStateElement extends UmbLitElement implements UmbTableColumnLayoutElement { - @property({ type: Object, attribute: false }) column!: UmbTableColumn; - - @property({ type: Object, attribute: false }) item!: UmbTableItem; @property({ attribute: false }) - value!: UmbDocumentCollectionItemModel; + value!: UmbEditableDocumentCollectionItemModel; render() { - switch (this.value.state) { + switch (this.value.item.state) { case 'Published': return html`${this.localize.term('content_published')}`; case 'PublishedPendingChanges': @@ -26,7 +23,7 @@ export class UmbDocumentTableColumnStateElement extends UmbLitElement implements case 'NotCreated': return html`${this.localize.term('content_notCreated')}`; default: - return html`${fromCamelCase(this.value.state)}`; + return html`${fromCamelCase(this.value.item.state)}`; } } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts index 41851a1a6a..73b81cbc49 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts @@ -1,11 +1,14 @@ import { getPropertyValueByAlias } from '../index.js'; +import { UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN } from '../../../paths.js'; import type { UmbCollectionColumnConfiguration } from '../../../../../core/collection/types.js'; import type { UmbDocumentCollectionItemModel } from '../../types.js'; import type { UmbDocumentCollectionContext } from '../../document-collection.context.js'; -import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { css, customElement, html, nothing, state, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection'; +import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal'; +import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/modal'; import type { UmbTableColumn, UmbTableConfig, @@ -59,29 +62,52 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement { @state() private _selection: Array = []; - @state() - private _skip: number = 0; - #collectionContext?: UmbDocumentCollectionContext; + #routeBuilder?: UmbModalRouteBuilder; + constructor() { super(); this.consumeContext(UMB_COLLECTION_CONTEXT, (collectionContext) => { this.#collectionContext = collectionContext; - this.#observeCollectionContext(); }); + + this.#registerModalRoute(); + } + + #registerModalRoute() { + new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) + .addAdditionalPath(':entityType') + .onSetup((params) => { + return { data: { entityType: params.entityType, preset: {} } }; + }) + .onReject(() => { + this.#collectionContext?.requestCollection(); + }) + .onSubmit(() => { + this.#collectionContext?.requestCollection(); + }) + .observeRouteBuilder((routeBuilder) => { + this.#routeBuilder = routeBuilder; + + // NOTE: Configuring the observations AFTER the route builder is ready, + // otherwise there is a race condition and `#collectionContext.items` tends to win. [LK] + this.#observeCollectionContext(); + }); } #observeCollectionContext() { if (!this.#collectionContext) return; + this.observe(this.#collectionContext.loading, (loading) => (this._loading = loading), '_observeLoading'); + this.observe( this.#collectionContext.userDefinedProperties, (userDefinedProperties) => { this._userDefinedProperties = userDefinedProperties; this.#createTableHeadings(); }, - 'umbCollectionUserDefinedPropertiesObserver', + '_observeUserDefinedProperties', ); this.observe( @@ -90,7 +116,7 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement { this._items = items; this.#createTableItems(this._items); }, - 'umbCollectionItemsObserver', + '_observeItems', ); this.observe( @@ -98,15 +124,7 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement { (selection) => { this._selection = selection as string[]; }, - 'umbCollectionSelectionObserver', - ); - - this.observe( - this.#collectionContext.pagination.skip, - (skip) => { - this._skip = skip; - }, - 'umbCollectionSkipObserver', + '_observeSelection', ); } @@ -131,15 +149,21 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement { const data = this._tableColumns?.map((column) => { + const editPath = this.#routeBuilder + ? this.#routeBuilder({ entityType: item.entityType }) + + UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN.generateLocal({ unique: item.unique }) + : ''; + return { columnAlias: column.alias, - value: column.elementName ? item : getPropertyValueByAlias(item, column.alias), + value: column.elementName ? { item, editPath } : getPropertyValueByAlias(item, column.alias), }; }) ?? []; return { id: item.unique, icon: item.icon, + entityType: 'document', data: data, }; }); @@ -170,23 +194,34 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement { } render() { - if (this._loading) { - return html`
`; - } + return this._tableItems.length === 0 ? this.#renderEmpty() : this.#renderItems(); + } - if (this._tableItems.length === 0) { - return html`

${this.localize.term('content_listViewNoItems')}

`; - } + #renderEmpty() { + if (this._tableItems.length > 0) return nothing; + return html` +
+ ${when( + this._loading, + () => html``, + () => html`

${this.localize.term('content_listViewNoItems')}

`, + )} +
+ `; + } + #renderItems() { + if (this._tableItems.length === 0) return nothing; return html` + @selected=${this.#handleSelect} + @deselected=${this.#handleDeselect} + @ordered=${this.#handleOrdering}> + ${when(this._loading, () => html``)} `; }