diff --git a/src/Umbraco.Web.UI.Client/devops/module-dependencies/index.js b/src/Umbraco.Web.UI.Client/devops/module-dependencies/index.js index bb8d82077f..d6e5febf2e 100644 --- a/src/Umbraco.Web.UI.Client/devops/module-dependencies/index.js +++ b/src/Umbraco.Web.UI.Client/devops/module-dependencies/index.js @@ -2,7 +2,7 @@ import fs from 'fs'; import path from 'path'; import { createImportMap } from '../importmap/index.js'; -const ILLEGAL_CORE_IMPORTS_THRESHOLD = 6; +const ILLEGAL_CORE_IMPORTS_THRESHOLD = 5; const SELF_IMPORTS_THRESHOLD = 0; const BIDIRECTIONAL_IMPORTS_THRESHOLD = 18; diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/constants.ts index 888a91de6a..f795cf937b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/constants.ts @@ -1,4 +1,5 @@ export const UMB_CONTENT_SECTION_ALIAS = 'Umb.Section.Content'; -export * from './workspace/constants.js'; -export * from './conditions/constants.js'; export * from './collection/constants.js'; +export * from './conditions/constants.js'; +export * from './tree/constants.js'; +export * from './workspace/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/manifests.ts index 2636fe20b9..e6f91beb4d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/manifests.ts @@ -1,5 +1,11 @@ -import { manifests as workspaceManifests } from './workspace/manifests.js'; -import { manifests as conditionManifests } from './conditions/manifests.js'; import { manifests as collectionManifests } from './collection/manifests.js'; +import { manifests as conditionManifests } from './conditions/manifests.js'; +import { manifests as contentTreeManifests } from './tree/manifests.js'; +import { manifests as workspaceManifests } from './workspace/manifests.js'; -export const manifests = [...workspaceManifests, ...conditionManifests, ...collectionManifests]; +export const manifests = [ + ...collectionManifests, + ...conditionManifests, + ...contentTreeManifests, + ...workspaceManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/constants.ts new file mode 100644 index 0000000000..fa561bdb6d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/constants.ts @@ -0,0 +1 @@ +export * from './sort-children-of-content/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/manifests.ts new file mode 100644 index 0000000000..10ff8e4621 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/manifests.ts @@ -0,0 +1,4 @@ +import { manifests as sortChildrenOfContentManifests } from './sort-children-of-content/manifests.js'; +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [...sortChildrenOfContentManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/constants.ts new file mode 100644 index 0000000000..26f4f0dd5f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/constants.ts @@ -0,0 +1 @@ +export * from './modal/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/index.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/index.ts new file mode 100644 index 0000000000..477925dac0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/index.ts @@ -0,0 +1,2 @@ +export * from './sort-children-of-content.action.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/manifests.ts new file mode 100644 index 0000000000..d232135c9e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/manifests.ts @@ -0,0 +1,9 @@ +import { manifest as sortChildrenOfContentKindManifest } from './sort-children-of-content.action.kind.js'; +import { manifests as modalManifests } from './modal/manifests.js'; + +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + sortChildrenOfContentKindManifest, + ...modalManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/modal/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/modal/constants.ts new file mode 100644 index 0000000000..c9ce2a3093 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/modal/constants.ts @@ -0,0 +1,4 @@ +export { + UMB_SORT_CHILDREN_OF_CONTENT_MODAL, + UMB_SORT_CHILDREN_OF_CONTENT_MODAL_ALIAS, +} from './sort-children-of-content-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/modal/index.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/modal/index.ts new file mode 100644 index 0000000000..2618a1cdd1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/modal/index.ts @@ -0,0 +1 @@ +export * from './sort-children-of-content-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/modal/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/modal/manifests.ts new file mode 100644 index 0000000000..a900e11faa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/modal/manifests.ts @@ -0,0 +1,11 @@ +import { UMB_SORT_CHILDREN_OF_CONTENT_MODAL_ALIAS } from './constants.js'; +import type { ManifestModal } from '@umbraco-cms/backoffice/modal'; + +export const manifests: Array = [ + { + type: 'modal', + alias: UMB_SORT_CHILDREN_OF_CONTENT_MODAL_ALIAS, + name: 'Sort Children Of Content Modal', + element: () => import('./sort-children-of-content-modal.element.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/modal/sort-children-of-content-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/modal/sort-children-of-content-modal.element.ts new file mode 100644 index 0000000000..551bc0ca62 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/modal/sort-children-of-content-modal.element.ts @@ -0,0 +1,74 @@ +import type { UmbContentTreeItemModel } from '../../types.js'; +import { customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbSortChildrenOfModalElement } from '@umbraco-cms/backoffice/tree'; + +@customElement('umb-sort-children-of-content-modal') +export class UmbSortChildrenOfContentModalElement extends UmbSortChildrenOfModalElement { + #localizeDateOptions: Intl.DateTimeFormatOptions = { + day: 'numeric', + month: 'short', + year: 'numeric', + hour: 'numeric', + minute: '2-digit', + }; + + protected override _setTableColumns() { + this._tableColumns = [ + { + name: this.localize.term('general_name'), + alias: 'name', + allowSorting: this._hasMorePages() === false, + }, + { + name: this.localize.term('content_createDate'), + alias: 'createDate', + allowSorting: this._hasMorePages() === false, + }, + ]; + } + + protected override _createTableItems() { + this._tableItems = this._children.map((treeItem) => { + // TODO: implement ItemDataResolver for document and media + // This will fix both the icon and the variant name + return { + id: treeItem.unique, + icon: 'icon-document', + data: [ + { + columnAlias: 'name', + value: treeItem.name, + }, + { + columnAlias: 'createDate', + value: this.localize.date(treeItem.createDate, this.#localizeDateOptions), + }, + ], + }; + }); + } + + protected override _sortCompare(columnAlias: string, valueA: unknown, valueB: unknown): number { + if (columnAlias === 'createDate') { + return Date.parse(valueA as string) - Date.parse(valueB as string); + } + + return super._sortCompare(columnAlias, valueA, valueB); + } + + protected override _onLoadMore(event: PointerEvent): void { + super._onLoadMore(event); + // TODO: make nicer API for enable/disable orderBy for individual columns + const allowOrderBy = this._hasMorePages() === false; + this._tableColumns[0].allowSorting = allowOrderBy; + this._tableColumns[1].allowSorting = allowOrderBy; + } +} + +export { UmbSortChildrenOfContentModalElement as element }; + +declare global { + interface HTMLElementTagNameMap { + ['umb-sort-children-of-content-modal']: UmbSortChildrenOfContentModalElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/modal/sort-children-of-content-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/modal/sort-children-of-content-modal.token.ts new file mode 100644 index 0000000000..fbd004a0bc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/modal/sort-children-of-content-modal.token.ts @@ -0,0 +1,14 @@ +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; +import type { UmbSortChildrenOfModalData, UmbSortChildrenOfModalValue } from '@umbraco-cms/backoffice/tree'; + +export const UMB_SORT_CHILDREN_OF_CONTENT_MODAL_ALIAS = 'Umb.Modal.SortChildrenOfContent'; + +export const UMB_SORT_CHILDREN_OF_CONTENT_MODAL = new UmbModalToken< + UmbSortChildrenOfModalData, + UmbSortChildrenOfModalValue +>(UMB_SORT_CHILDREN_OF_CONTENT_MODAL_ALIAS, { + modal: { + type: 'sidebar', + size: 'small', + }, +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/sort-children-of-content.action.kind.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/sort-children-of-content.action.kind.ts new file mode 100644 index 0000000000..f7b00a735c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/sort-children-of-content.action.kind.ts @@ -0,0 +1,15 @@ +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; +import { UMB_ENTITY_ACTION_SORT_CHILDREN_OF_KIND_MANIFEST } from '@umbraco-cms/backoffice/tree'; + +export const manifest: UmbExtensionManifestKind = { + type: 'kind', + alias: 'Umb.Kind.EntityAction.SortChildrenOfContent', + matchKind: 'sortChildrenOfContent', + matchType: 'entityAction', + manifest: { + ...UMB_ENTITY_ACTION_SORT_CHILDREN_OF_KIND_MANIFEST.manifest, + type: 'entityAction', + kind: 'sortChildrenOfContent', + api: () => import('./sort-children-of-content.action.js'), + }, +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/sort-children-of-content.action.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/sort-children-of-content.action.ts new file mode 100644 index 0000000000..19d2d2bc3c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/sort-children-of-content.action.ts @@ -0,0 +1,20 @@ +import { UMB_SORT_CHILDREN_OF_CONTENT_MODAL } from './constants.js'; +import type { UmbModalToken } from '@umbraco-cms/backoffice/modal'; +import { + UmbSortChildrenOfEntityAction, + type UmbSortChildrenOfModalData, + type UmbSortChildrenOfModalValue, +} from '@umbraco-cms/backoffice/tree'; + +/** + * Entity action for sorting children of a content item + * @class UmbSortChildrenOfContentEntityAction + * @augments UmbSortChildrenOfEntityAction + */ +export class UmbSortChildrenOfContentEntityAction extends UmbSortChildrenOfEntityAction { + protected override _getModalToken(): UmbModalToken { + return UMB_SORT_CHILDREN_OF_CONTENT_MODAL; + } +} + +export { UmbSortChildrenOfContentEntityAction as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/types.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/types.ts new file mode 100644 index 0000000000..a38bd9ae05 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/sort-children-of-content/types.ts @@ -0,0 +1,14 @@ +import type { ManifestEntityAction } from '@umbraco-cms/backoffice/entity-action'; +import type { MetaEntityActionSortChildrenOfKind } from '@umbraco-cms/backoffice/tree'; + +export interface ManifestEntityActionSortChildrenOfContentKind + extends ManifestEntityAction { + type: 'entityAction'; + kind: 'sortChildrenOfContent'; +} + +declare global { + interface UmbExtensionManifestMap { + umbManifestEntityActionSortChildrenOfContentKind: ManifestEntityActionSortChildrenOfContentKind; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/types.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/types.ts new file mode 100644 index 0000000000..a252ebcf1f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/tree/types.ts @@ -0,0 +1,10 @@ +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import type { UmbTreeItemModel } from '@umbraco-cms/backoffice/tree'; + +export type * from './sort-children-of-content/types.js'; + +export interface UmbContentTreeItemModel extends UmbTreeItemModel { + ancestors: Array; + entityType: string; + createDate: string; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/types.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/types.ts index 0a3e945fca..aee796bc65 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/types.ts @@ -2,6 +2,7 @@ import type { UmbPropertyValueData } from '@umbraco-cms/backoffice/property'; import type { UmbEntityVariantModel } from '@umbraco-cms/backoffice/variant'; export type * from './collection/types.js'; +export type * from './tree/types.js'; export interface UmbElementDetailModel { values: Array; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.ts index 04b4475cc3..b7dae8ab0d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/table/table.element.ts @@ -8,11 +8,11 @@ import { repeat, state, when, - LitElement, } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -// TODO: move to UI Library - entity actions should NOT be moved to UI Library but stay in an UmbTable element export interface UmbTableItem { id: string; icon?: string | null; @@ -64,6 +64,19 @@ export class UmbTableOrderedEvent extends Event { } } +export class UmbTableSortedEvent extends Event { + #itemId: string; + + public constructor({ itemId }: { itemId: string }) { + super('sorted', { bubbles: true, composed: true }); + this.#itemId = itemId; + } + + public getItemId() { + return this.#itemId; + } +} + /** * @element umb-table * @description - Element for displaying a table @@ -73,14 +86,21 @@ export class UmbTableOrderedEvent extends Event { * @augments LitElement */ @customElement('umb-table') -export class UmbTableElement extends LitElement { +export class UmbTableElement extends UmbLitElement { /** * Table Items * @type {Array} * @memberof UmbTableElement */ @property({ type: Array, attribute: false }) - public items: Array = []; + private _items: Array = []; + public get items(): Array { + return this._items; + } + public set items(value: Array) { + this._items = value; + this.#sorter.setModel(value); + } /** * @description Table Columns @@ -115,9 +135,53 @@ export class UmbTableElement extends LitElement { @property({ type: Boolean, attribute: false }) public orderingDesc = false; + private _sortable = false; + @property({ type: Boolean, reflect: true }) + get sortable() { + return this._sortable; + } + set sortable(newVal) { + const oldVal = this._sortable; + if (oldVal === newVal) return; + this._sortable = newVal; + + if (this._sortable) { + this.#sorter.enable(); + } else { + this.#sorter.disable(); + } + + this.requestUpdate('sortable', oldVal); + } + @state() private _selectionMode = false; + #sorter = new UmbSorterController(this, { + getUniqueOfElement: (element) => { + return element.dataset.sortableId; + }, + getUniqueOfModel: (item) => { + return item.id; + }, + identifier: 'Umb.SorterIdentifier.UmbTable', + itemSelector: 'uui-table-row', + containerSelector: 'uui-table', + onChange: ({ model }) => { + const oldValue = this.items; + this.items = model; + this.requestUpdate('items', oldValue); + }, + onEnd: ({ item }) => { + this.dispatchEvent(new UmbTableSortedEvent({ itemId: item.id })); + }, + }); + + constructor() { + super(); + this.#sorter.disable(); + } + private _isSelected(key: string) { return this.selection.includes(key); } @@ -230,7 +294,8 @@ export class UmbTableElement extends LitElement { private _renderRow = (item: UmbTableItem) => { return html` this._selectRow(item.id)} @@ -241,6 +306,12 @@ export class UmbTableElement extends LitElement { }; private _renderRowCheckboxCell(item: UmbTableItem) { + if (this.sortable === true) { + return html` + + `; + } + if (this.config.hideIcon && !this.config.allowSelection) return; return html` @@ -301,6 +372,16 @@ export class UmbTableElement extends LitElement { height: fit-content; } + :host([sortable]) { + uui-table-row:hover { + cursor: grab; + } + + uui-table-row:active { + cursor: grabbing; + } + } + uui-table { box-shadow: var(--uui-shadow-depth-1); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/constants.ts index 26f4f0dd5f..4a48ab6755 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/constants.ts @@ -1 +1,2 @@ export * from './modal/constants.js'; +export { UMB_ENTITY_ACTION_SORT_CHILDREN_OF_KIND_MANIFEST } from './sort-children-of.action.kind.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/index.ts index 2e789e1917..cbd2cb8fc6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/index.ts @@ -1 +1,2 @@ export { UmbSortChildrenOfEntityAction } from './sort-children-of.action.js'; +export * from './modal/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/modal/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/modal/index.ts index 0f60af4a16..edea2c055d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/modal/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/modal/index.ts @@ -1 +1,2 @@ export * from './sort-children-of-modal.token.js'; +export * from './sort-children-of-modal.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/modal/sort-children-of-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/modal/sort-children-of-modal.element.ts index 2fae7f00d1..57ef96988f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/modal/sort-children-of-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/modal/sort-children-of-modal.element.ts @@ -2,25 +2,29 @@ import type { UmbSortChildrenOfRepository, UmbTreeItemModel } from '../../../typ import type { UmbTreeRepository } from '../../../data/index.js'; import type { UmbSortChildrenOfModalData, UmbSortChildrenOfModalValue } from './sort-children-of-modal.token.js'; import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; -import { html, customElement, css, state, repeat, nothing } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, css, state, nothing } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; -import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry'; import { UmbPaginationManager } from '@umbraco-cms/backoffice/utils'; import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; -import type { UmbDocumentTreeItemModel } from '@umbraco-cms/backoffice/document'; -import type { UmbMediaTreeItemModel } from '@umbraco-cms/backoffice/media'; +import type { + UmbTableColumn, + UmbTableConfig, + UmbTableElement, + UmbTableItem, + UmbTableOrderedEvent, + UmbTableSortedEvent, +} from '@umbraco-cms/backoffice/components'; const elementName = 'umb-sort-children-of-modal'; @customElement(elementName) -export class UmbSortChildrenOfModalElement extends UmbModalBaseElement< - UmbSortChildrenOfModalData, - UmbSortChildrenOfModalValue -> { +export class UmbSortChildrenOfModalElement< + TreeItemModelType extends UmbTreeItemModel = UmbTreeItemModel, +> extends UmbModalBaseElement { @state() - _children: Array = []; + protected _children: Array = []; @state() _currentPage = 1; @@ -29,26 +33,22 @@ export class UmbSortChildrenOfModalElement extends UmbModalBaseElement< _totalPages = 1; @state() - _isSorting: boolean = false; + protected _tableColumns: Array = []; - #hasMorePages() { - return this._currentPage < this._totalPages; - } + @state() + private _tableConfig: UmbTableConfig = { + allowSelection: false, + }; + + @state() + protected _tableItems: Array = []; + + @state() + private _sortable = false; + + protected _sortedUniques = new Set(); #pagination = new UmbPaginationManager(); - #sortedUniques = new Set(); - #sorter?: UmbSorterController; - - #sortBy: string = ''; - #sortDirection: string = ''; - - #localizeDateOptions: Intl.DateTimeFormatOptions = { - day: 'numeric', - month: 'short', - year: 'numeric', - hour: 'numeric', - minute: '2-digit', - }; constructor() { super(); @@ -64,18 +64,29 @@ export class UmbSortChildrenOfModalElement extends UmbModalBaseElement< ); } + protected _setTableColumns() { + this._tableColumns = [ + { + name: this.localize.term('general_name'), + alias: 'name', + allowSorting: true, + }, + ]; + } + protected override async firstUpdated( _changedProperties: PropertyValueMap | Map, ): Promise { super.firstUpdated(_changedProperties); - this.#requestChildren(); + await this.#requestChildren(); + this._setTableColumns(); } async #requestChildren() { if (this.data?.unique === undefined) throw new Error('unique is required'); if (!this.data?.treeRepositoryAlias) throw new Error('treeRepositoryAlias is required'); - const treeRepository = await createExtensionApiByAlias>( + const treeRepository = await createExtensionApiByAlias>( this, this.data.treeRepositoryAlias, ); @@ -92,39 +103,31 @@ export class UmbSortChildrenOfModalElement extends UmbModalBaseElement< if (data) { this._children = [...this._children, ...data.items]; this.#pagination.setTotalItems(data.total); - - if (data.total > 0) { - this.#initSorter(); - this.#sorter?.setModel(this._children); - } + this._sortable = this._children.length > 0; + this._createTableItems(); } } - #initSorter() { - if (this.#sorter) return; - - this.#sorter = new UmbSorterController(this, { - getUniqueOfElement: (element) => { - return element.dataset.unique; - }, - getUniqueOfModel: (modelEntry) => { - return modelEntry.unique; - }, - identifier: 'Umb.SorterIdentifier.SortChildrenOfModal', - itemSelector: 'uui-table-row', - containerSelector: 'uui-table', - onChange: ({ model }) => { - const oldValue = this._children; - this._children = model; - this.requestUpdate('_children', oldValue); - }, - onEnd: ({ item }) => { - this.#sortedUniques.add(item.unique); - }, + protected _createTableItems() { + this._tableItems = this._children.map((treeItem) => { + return { + id: treeItem.unique, + icon: 'icon-globe', + data: [ + { + columnAlias: 'name', + value: html`${treeItem.name}`, + }, + ], + }; }); } - #onLoadMore(event: PointerEvent) { + protected _hasMorePages() { + return this._currentPage < this._totalPages; + } + + protected _onLoadMore(event: PointerEvent) { event.stopPropagation(); if (this._currentPage >= this._totalPages) return; this.#pagination.setCurrentPageNumber(this._currentPage + 1); @@ -150,54 +153,12 @@ export class UmbSortChildrenOfModalElement extends UmbModalBaseElement< } } - #onSortChildrenBy(key: string) { - if (this._isSorting) { - return; - } - - this._isSorting = true; - - const oldValue = this._children; - - // If switching column, revert to ascending sort. Otherwise switch from whatever was previously selected. - if (this.#sortBy !== key) { - this.#sortDirection = 'asc'; - } else { - this.#sortDirection = this.#sortDirection === 'asc' ? 'desc' : 'asc'; - } - - // Sort by the new column. - this.#sortBy = key; - this._children = [...this._children].sort((a, b) => { - switch (key) { - case 'name': - return a.name.localeCompare(b.name); - case 'createDate': - return Date.parse(this.#getCreateDate(a)) - Date.parse(this.#getCreateDate(b)); - default: - return 0; - } - }); - - // Reverse the order if sorting descending. - if (this.#sortDirection === 'desc') { - this._children.reverse(); - } - - this.#sortedUniques.clear(); - this._children.map((c) => c.unique).forEach((u) => this.#sortedUniques.add(u)); - - this.requestUpdate('_children', oldValue); - - this._isSorting = false; - } - #getSortOrderOfSortedItems() { const sorting = []; // get the new sort order from the sorted uniques - for (const value of this.#sortedUniques) { - const index = this._children.findIndex((child) => child.unique === value); + for (const value of this._sortedUniques) { + const index = this._tableItems.findIndex((tableItem) => tableItem.id === value); if (index !== -1) { const entry = { unique: value, @@ -211,143 +172,82 @@ export class UmbSortChildrenOfModalElement extends UmbModalBaseElement< return sorting; } - #getCreateDate(item: UmbTreeItemModel): string { - let date = ''; - const itemAsDocumentTreeItemModel = item as UmbDocumentTreeItemModel; - if (itemAsDocumentTreeItemModel) { - date = itemAsDocumentTreeItemModel.createDate; - } else { - const itemAsMediaTreeItemModel = item as UmbMediaTreeItemModel; - if (itemAsMediaTreeItemModel) { - date = itemAsMediaTreeItemModel.createDate; - } + #onSorted(event: UmbTableSortedEvent) { + event.stopPropagation(); + const sortedId = event.getItemId(); + this._sortedUniques.add(sortedId); + const target = event.target as UmbTableElement; + const items = target.items; + this._tableItems = items; + } + + #onOrdered(event: UmbTableOrderedEvent) { + event.stopPropagation(); + const target = event.target as UmbTableElement; + const orderingColumn = target.orderingColumn; + const orderingDesc = target.orderingDesc; + + this._tableItems = [...this._tableItems].sort((a, b) => { + const aColumn = a.data.find((column) => column.columnAlias === orderingColumn); + const bColumn = b.data.find((column) => column.columnAlias === orderingColumn); + return this._sortCompare(orderingColumn, aColumn?.value, bColumn?.value); + }); + + if (orderingDesc) { + this._tableItems.reverse(); } - return date; + this._sortedUniques.clear(); + this._tableItems.map((tableItem) => tableItem.id).forEach((u) => this._sortedUniques.add(u)); + } + + protected _sortCompare(columnAlias: string, valueA: unknown, valueB: unknown): number { + if (columnAlias === 'name') { + return (valueA as string).localeCompare(valueB as string); + } + return 0; } override render() { return html` - ${this.#renderChildren()} + ${this._children.length === 0 ? this.#renderEmptyState() : this.#renderTable()} `; } - #renderChildren() { - if (this._children.length === 0) return html`There are no children`; - return html` - - - - ${this.#renderHeaderCell('name', 'general_name')} - ${this.#renderHeaderCell('createDate', 'content_createDate')} - - ${this._isSorting - ? html` - - - - - - ` - : nothing} - ${repeat( - this._children, - (child) => child.unique, - (child) => this.#renderChild(child), - )} - + #renderEmptyState() { + return html`There are no children`; + } - ${this.#hasMorePages() + #renderTable() { + return html` + + + ${this._hasMorePages() ? html` - Load More (${this._currentPage}/${this._totalPages})Load more (${this._currentPage}/${this._totalPages}) ` : nothing} `; } - #renderHeaderCell(key: string, labelKey: string) { - // Only provide buttons for sorting via the column headers if all pages have been loaded. - return html` - ${this.#hasMorePages() - ? html` ${this.localize.term(labelKey)} ` - : html` - - `} - `; - } - - #renderChild(item: UmbTreeItemModel) { - // TODO: find a way to get the icon for the item. We do not have the icon in the tree item model. - return html` - - ${item.name} - ${this.#renderCreateDate(item)} - `; - } - - #renderCreateDate(item: UmbTreeItemModel) { - const date = this.#getCreateDate(item); - if (date.length === 0) { - return nothing; - } - - return html``; - } - static override styles = [ UmbTextStyles, css` #loadMoreButton { width: 100%; - } - - uui-table-cell { - padding: var(--uui-size-space-2) var(--uui-size-space-5); - } - - uui-table-head-cell { - padding: 0 var(--uui-size-space-5); - } - - uui-table-head-cell button { - background-color: transparent; - color: inherit; - border: none; - cursor: pointer; - font-weight: inherit; - font-size: inherit; - display: inline-flex; - align-items: center; - justify-content: space-between; - width: 100%; - padding: var(--uui-size-5) var(--uui-size-1); - } - - uui-table-row.hidden { - visibility: hidden; - } - - uui-table-row[id='content-node']:hover { - cursor: grab; - } - - uui-table-row[id='content-node']:active { - cursor: grabbing; - } - - uui-box { - --uui-box-default-padding: 0; + margin-top: var(--uui-size-space-3); } `, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/modal/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/modal/types.ts new file mode 100644 index 0000000000..cec48fd607 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/modal/types.ts @@ -0,0 +1 @@ +export type * from './sort-children-of-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/sort-children-of.action.kind.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/sort-children-of.action.kind.ts index f8a0b3139e..193460d670 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/sort-children-of.action.kind.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/sort-children-of.action.kind.ts @@ -1,7 +1,7 @@ import { UMB_ENTITY_ACTION_DEFAULT_KIND_MANIFEST } from '@umbraco-cms/backoffice/entity-action'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; -export const manifest: UmbExtensionManifestKind = { +export const UMB_ENTITY_ACTION_SORT_CHILDREN_OF_KIND_MANIFEST: UmbExtensionManifestKind = { type: 'kind', alias: 'Umb.Kind.EntityAction.SortChildrenOf', matchKind: 'sortChildrenOf', @@ -22,3 +22,5 @@ export const manifest: UmbExtensionManifestKind = { }, }, }; + +export const manifest = UMB_ENTITY_ACTION_SORT_CHILDREN_OF_KIND_MANIFEST; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/sort-children-of.action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/sort-children-of.action.ts index 1289b41a60..6cef423bce 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/sort-children-of.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/entity-actions/sort-children-of/sort-children-of.action.ts @@ -6,7 +6,7 @@ import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; export class UmbSortChildrenOfEntityAction extends UmbEntityActionBase { override async execute() { - await umbOpenModal(this, UMB_SORT_CHILDREN_OF_MODAL, { + await umbOpenModal(this, this._getModalToken(), { data: { unique: this.args.unique, entityType: this.args.entityType, @@ -25,6 +25,10 @@ export class UmbSortChildrenOfEntityAction extends UmbEntityActionBase = [ ...repositoryManifests, { type: 'entityAction', - kind: 'sortChildrenOf', + kind: 'sortChildrenOfContent', alias: 'Umb.EntityAction.Document.SortChildrenOf', name: 'Sort Children Of Document Entity Action', forEntityTypes: [UMB_DOCUMENT_ROOT_ENTITY_TYPE, UMB_DOCUMENT_ENTITY_TYPE], diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/sort-children-of/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/sort-children-of/manifests.ts index d0ab6a49e8..b0894dcff0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/sort-children-of/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/sort-children-of/manifests.ts @@ -8,7 +8,7 @@ export const manifests: Array = [ ...repositoryManifests, { type: 'entityAction', - kind: 'sortChildrenOf', + kind: 'sortChildrenOfContent', alias: 'Umb.EntityAction.Media.SortChildrenOf', name: 'Sort Children Of Media Entity Action', forEntityTypes: [UMB_MEDIA_ROOT_ENTITY_TYPE, UMB_MEDIA_ENTITY_TYPE],