V15: Document Type Create Options (#17669)

* Refactors `umb-ref-item` to inherit from `uui-ref-node`

To extend, rather than reinvent the wheel.

* Updates components using `umb-ref-item` with `select-only` attribute

* Updates `umb-entity-create-option-action-list-modal` to use `umb-ref-item`

instead of `uui-ref-node`, so we can use `umb-icon`
(with color support) and UI consistency.

* Adds `headline` property for Create Option modal

* Changes 'Umb.EntityAction.DocumentType.Create' to use `kind: 'create'`

Deprecates `umb-document-type-create-options-modal` and token.

* Adds `entityCreateOptionAction` extensions for Document Types

- Document Type (default)
- Document Type with Template
- Element Type
- Folder

* Tweaks Create Options modal to submit upon selection

* corrections

* remove headline option

---------

Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
Co-authored-by: Mads Rasmussen <madsr@hey.com>
This commit is contained in:
Lee Kelleher
2025-01-07 12:28:00 +00:00
committed by GitHub
parent 263a6d8d61
commit 1fdf32f404
13 changed files with 186 additions and 78 deletions

View File

@@ -1,66 +1,29 @@
import { html, customElement, css, property, when } from '@umbraco-cms/backoffice/external/lit';
import { customElement, css, property, type PropertyValues } from '@umbraco-cms/backoffice/external/lit';
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UUIRefElement, UUIRefEvent, UUIRefNodeElement } from '@umbraco-cms/backoffice/external/uui';
import { UUIRefNodeElement } from '@umbraco-cms/backoffice/external/uui';
@customElement('umb-ref-item')
export class UmbRefItemElement extends UmbElementMixin(UUIRefElement) {
@property({ type: String })
name = '';
@property({ type: String })
detail = '';
export class UmbRefItemElement extends UmbElementMixin(UUIRefNodeElement) {
@property({ type: String })
icon = '';
constructor() {
super();
#iconElement = document.createElement('umb-icon');
this.selectable = true;
protected override firstUpdated(_changedProperties: PropertyValues): void {
super.firstUpdated(_changedProperties);
this.addEventListener(UUIRefEvent.OPEN, () => this.dispatchEvent(new Event('click')));
}
override render() {
return html`
<button
type="button"
id="btn-item"
tabindex="0"
@click=${this.handleOpenClick}
@keydown=${this.handleOpenKeydown}
?disabled=${this.disabled}>
${when(this.icon, () => html`<span id="icon"><umb-icon name=${this.icon ?? ''}></umb-icon></span>`)}
<div id="info">
<div id="name">${this.name}</div>
<small id="detail">${this.detail}</small>
</div>
</button>
<div id="select-border"></div>
<slot></slot>
`;
// Temporary fix for the icon appending, this could in the future be changed to override a renderIcon method, or other ways to make this happen without appending children.
this.#iconElement.setAttribute('slot', 'icon');
this.#iconElement.setAttribute('name', this.icon);
this.appendChild(this.#iconElement);
}
static override styles = [
...UUIRefElement.styles,
...UUIRefNodeElement.styles,
UmbTextStyles,
css`
:host {
padding: calc(var(--uui-size-4) + 1px);
}
#btn-item {
text-decoration: none;
color: inherit;
align-self: stretch;
line-height: normal;
display: flex;
position: relative;
align-items: center;
cursor: pointer;
padding-top: var(--uui-size-3);
padding-bottom: var(--uui-size-3);
}
`,
];

View File

@@ -18,8 +18,7 @@ import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
type ManifestType = ManifestEntityCreateOptionAction;
const elementName = 'umb-entity-create-option-action-list-modal';
@customElement(elementName)
@customElement('umb-entity-create-option-action-list-modal')
export class UmbEntityCreateOptionActionListModalElement extends UmbModalBaseElement<
UmbEntityCreateOptionActionListModalData,
UmbEntityCreateOptionActionListModalValue
@@ -71,6 +70,8 @@ export class UmbEntityCreateOptionActionListModalElement extends UmbModalBaseEle
if (!controller.api) throw new Error('No API found');
await controller.api.execute();
this._submitModal();
}
#getTarget(href?: string) {
@@ -83,17 +84,19 @@ export class UmbEntityCreateOptionActionListModalElement extends UmbModalBaseEle
override render() {
return html`
<umb-body-layout headline="${this.localize.term('user_createUser')}">
<umb-body-layout headline="${this.localize.term('general_create')}">
<uui-box>
${this._apiControllers.length === 0
? html`<div>No create options available.</div>`
: html`<uui-ref-list>
${repeat(
this._apiControllers,
(controller) => controller.manifest?.alias,
(controller, index) => this.#renderRefItem(controller, index),
)}
</uui-ref-list>`}
: html`
<uui-ref-list>
${repeat(
this._apiControllers,
(controller) => controller.manifest?.alias,
(controller, index) => this.#renderRefItem(controller, index),
)}
</uui-ref-list>
`}
</uui-box>
<uui-button
slot="actions"
@@ -108,19 +111,18 @@ export class UmbEntityCreateOptionActionListModalElement extends UmbModalBaseEle
if (!manifest) throw new Error('No manifest found');
const label = manifest.meta.label ? this.localize.string(manifest.meta.label) : manifest.name;
const description = manifest.meta.description ? this.localize.string(manifest.meta.description) : undefined;
const href = this._hrefList[index];
return html`
<uui-ref-node
<umb-ref-item
name=${label}
detail=${ifDefined(manifest.meta.description)}
@click=${(event: Event) => this.#onClick(event, controller, href)}
detail=${ifDefined(description)}
icon=${manifest.meta.icon}
href=${ifDefined(href)}
target=${this.#getTarget(href)}
?selectable=${!href}
?readonly=${!href}>
<uui-icon slot="icon" name=${manifest.meta.icon}></uui-icon>
</uui-ref-node>
@open=${(event: Event) => this.#onClick(event, controller, href)}>
</umb-ref-item>
`;
}
}
@@ -129,6 +131,6 @@ export { UmbEntityCreateOptionActionListModalElement as element };
declare global {
interface HTMLElementTagNameMap {
[elementName]: UmbEntityCreateOptionActionListModalElement;
'umb-entity-create-option-action-list-modal': UmbEntityCreateOptionActionListModalElement;
}
}

View File

@@ -75,7 +75,7 @@ export class UmbItemPickerModalElement extends UmbModalBaseElement<UmbItemPicker
name=${this.localize.string(item.label)}
detail=${ifDefined(item.description)}
icon=${ifDefined(item.icon)}
@click=${() => this.#submit(item)}>
@open=${() => this.#submit(item)}>
</umb-ref-item>
`,
)}

View File

@@ -53,6 +53,7 @@ export class UmbDefaultPickerSearchResultItemElement extends UmbLitElement {
name=${item.name}
id=${item.unique}
icon=${item.icon ?? 'icon-document'}
select-only
selectable
@selected=${() => this.#pickerContext?.selection.select(item.unique)}
@deselected=${() => this.#pickerContext?.selection.deselect(item.unique)}

View File

@@ -0,0 +1,21 @@
import { UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN } from '../../../paths.js';
import type { UmbDocumentTypeEntityTypeUnion } from '../../../entity.js';
import { UmbEntityCreateOptionActionBase } from '@umbraco-cms/backoffice/entity-create-option-action';
import type { MetaEntityCreateOptionAction } from '@umbraco-cms/backoffice/entity-create-option-action';
export class UmbDefaultDocumentTypeCreateOptionAction extends UmbEntityCreateOptionActionBase<MetaEntityCreateOptionAction> {
override async getHref() {
const parentEntityType = this.args.entityType as UmbDocumentTypeEntityTypeUnion;
if (!parentEntityType) throw new Error('Entity type is required to create a document type');
const parentUnique = this.args.unique ?? null;
return UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN.generateAbsolute({
parentEntityType,
parentUnique,
presetAlias: null,
});
}
}
export { UmbDefaultDocumentTypeCreateOptionAction as api };

View File

@@ -0,0 +1,24 @@
import {
UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN,
UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PRESET_ELEMENT,
} from '../../../paths.js';
import type { UmbDocumentTypeEntityTypeUnion } from '../../../entity.js';
import { UmbEntityCreateOptionActionBase } from '@umbraco-cms/backoffice/entity-create-option-action';
import type { MetaEntityCreateOptionAction } from '@umbraco-cms/backoffice/entity-create-option-action';
export class UmbElementTypeCreateOptionAction extends UmbEntityCreateOptionActionBase<MetaEntityCreateOptionAction> {
override async getHref() {
const parentEntityType = this.args.entityType as UmbDocumentTypeEntityTypeUnion;
if (!parentEntityType) throw new Error('Entity type is required to create a document type');
const parentUnique = this.args.unique ?? null;
return UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN.generateAbsolute({
parentEntityType,
parentUnique,
presetAlias: UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PRESET_ELEMENT,
});
}
}
export { UmbElementTypeCreateOptionAction as api };

View File

@@ -0,0 +1,12 @@
import { UMB_DOCUMENT_TYPE_FOLDER_REPOSITORY_ALIAS } from '../../../tree/folder/repository/constants.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbCreateFolderEntityAction, type MetaEntityActionFolderKind } from '@umbraco-cms/backoffice/tree';
import type { UmbEntityActionArgs } from '@umbraco-cms/backoffice/entity-action';
export class UmbDocumentTypeFolderCreateOptionAction extends UmbCreateFolderEntityAction {
constructor(host: UmbControllerHost, args: UmbEntityActionArgs<MetaEntityActionFolderKind>) {
super(host, { ...args, meta: { ...args.meta, folderRepositoryAlias: UMB_DOCUMENT_TYPE_FOLDER_REPOSITORY_ALIAS } });
}
}
export { UmbDocumentTypeFolderCreateOptionAction as api };

View File

@@ -1,25 +1,83 @@
import { UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE } from '../../entity.js';
import { UMB_DOCUMENT_TYPE_ENTITY_TYPE, UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE } from '../../entity.js';
import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from '../../tree/index.js';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'entityAction',
kind: 'default',
kind: 'create',
alias: 'Umb.EntityAction.DocumentType.Create',
name: 'Create Document Type Entity Action',
weight: 1200,
api: () => import('./create.action.js'),
forEntityTypes: [UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE, UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE],
meta: {
icon: 'icon-add',
label: '#actions_create',
additionalOptions: true,
headline: '#create_createUnder #treeHeaders_documentTypes',
},
},
{
type: 'modal',
alias: 'Umb.Modal.DocumentTypeCreateOptions',
name: 'Document Type Create Options Modal',
element: () => import('./modal/document-type-create-options-modal.element.js'),
type: 'entityCreateOptionAction',
alias: 'Umb.EntityCreateOptionAction.DocumentType.Default',
name: 'Default Document Type Entity Create Option Action',
weight: 100,
api: () => import('./default/default-document-type-create-option-action.js'),
forEntityTypes: [
UMB_DOCUMENT_TYPE_ENTITY_TYPE,
UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE,
UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE,
],
meta: {
icon: 'icon-document',
label: '#create_documentType',
description: '#create_documentTypeDescription',
},
},
{
type: 'entityCreateOptionAction',
alias: 'Umb.EntityCreateOptionAction.DocumentType.DocumentWithTemplate',
name: 'Document Type with Template Document Type Entity Create Option Action',
weight: 90,
api: () => import('./template/document-type-with-template-create-option-action.js'),
forEntityTypes: [
UMB_DOCUMENT_TYPE_ENTITY_TYPE,
UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE,
UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE,
],
meta: {
icon: 'icon-document-html',
label: '#create_documentTypeWithTemplate',
description: '#create_documentTypeWithTemplateDescription',
},
},
{
type: 'entityCreateOptionAction',
alias: 'Umb.EntityCreateOptionAction.DocumentType.ElementType',
name: 'Element Type Document Type Entity Create Option Action',
weight: 80,
api: () => import('./element/element-type-create-option-action.js'),
forEntityTypes: [
UMB_DOCUMENT_TYPE_ENTITY_TYPE,
UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE,
UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE,
],
meta: {
icon: 'icon-plugin',
label: '#create_elementType',
description: '#create_elementTypeDescription',
},
},
{
type: 'entityCreateOptionAction',
alias: 'Umb.EntityCreateOptionAction.DocumentType.Folder',
name: 'Folder Document Type Entity Create Option Action',
weight: 70,
api: () => import('./folder/document-type-folder-create-option-action.js'),
forEntityTypes: [UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE, UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE],
meta: {
icon: 'icon-folder',
label: '#create_folder',
description: '#create_folderDescription',
},
},
];

View File

@@ -1,10 +1,12 @@
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
/** @deprecated No longer used internally. This will be removed in Umbraco 17. [LK] */
export interface UmbDocumentTypeCreateOptionsModalData {
parent: UmbEntityModel;
}
/** @deprecated No longer used internally. This will be removed in Umbraco 17. [LK] */
export const UMB_DOCUMENT_TYPE_CREATE_OPTIONS_MODAL = new UmbModalToken<UmbDocumentTypeCreateOptionsModalData>(
'Umb.Modal.DocumentTypeCreateOptions',
{

View File

@@ -12,6 +12,7 @@ import { UmbCreateFolderEntityAction } from '@umbraco-cms/backoffice/tree';
// Include the types from the DocumentTypeWorkspacePresetType + folder.
type OptionsPresetType = UmbCreateDocumentTypeWorkspacePresetType | 'folder' | null;
/** @deprecated No longer used internally. This will be removed in Umbraco 17. [LK] */
@customElement('umb-document-type-create-options-modal')
export class UmbDataTypeCreateOptionsModalElement extends UmbModalBaseElement<UmbDocumentTypeCreateOptionsModalData> {
#createFolderAction?: UmbCreateFolderEntityAction;
@@ -110,7 +111,7 @@ export class UmbDataTypeCreateOptionsModalElement extends UmbModalBaseElement<Um
name=${item.label}
detail=${item.description}
icon=${item.icon}
@click=${() => this.#onClick(item.preset)}></umb-ref-item>
@open=${() => this.#onClick(item.preset)}></umb-ref-item>
`,
)}
</uui-ref-list>

View File

@@ -0,0 +1,24 @@
import {
UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN,
UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PRESET_TEMPLATE,
} from '../../../paths.js';
import type { UmbDocumentTypeEntityTypeUnion } from '../../../entity.js';
import { UmbEntityCreateOptionActionBase } from '@umbraco-cms/backoffice/entity-create-option-action';
import type { MetaEntityCreateOptionAction } from '@umbraco-cms/backoffice/entity-create-option-action';
export class UmbDocumentTypeWithTemplateCreateOptionAction extends UmbEntityCreateOptionActionBase<MetaEntityCreateOptionAction> {
override async getHref() {
const parentEntityType = this.args.entityType as UmbDocumentTypeEntityTypeUnion;
if (!parentEntityType) throw new Error('Entity type is required to create a document type');
const parentUnique = this.args.unique ?? null;
return UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN.generateAbsolute({
parentEntityType,
parentUnique,
presetAlias: UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PRESET_TEMPLATE,
});
}
}
export { UmbDocumentTypeWithTemplateCreateOptionAction as api };

View File

@@ -76,7 +76,7 @@ export class UmbDynamicRootOriginPickerModalModalElement extends UmbModalBaseEle
name=${ifDefined(item.meta.label)}
detail=${ifDefined(item.meta.description)}
icon=${ifDefined(item.meta.icon)}
@click=${() => this.#choose(item)}></umb-ref-item>
@open=${() => this.#choose(item)}></umb-ref-item>
`,
)}
</uui-ref-list>

View File

@@ -63,7 +63,7 @@ export class UmbDynamicRootQueryStepPickerModalModalElement extends UmbModalBase
name=${ifDefined(item.meta.label)}
detail=${ifDefined(item.meta.description)}
icon=${ifDefined(item.meta.icon)}
@click=${() => this.#choose(item)}></umb-ref-item>
@open=${() => this.#choose(item)}></umb-ref-item>
`,
)}
</uui-ref-list>