Backoffice Item Pickers: Show error for missing items in 10 picker types (closes #19329, #20270, #20367) (#20762)
* Add errorDetail property to umb-entity-item-ref Add optional errorDetail property to display additional context (such as file paths or IDs) in error states. This enhances the error display to show both the error message and relevant details. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Make _removeItem protected in UmbPickerInputContext Change #removeItem from private to protected to allow subclasses to reuse the removal logic while customizing the confirmation dialog. This enables better extensibility for specialized picker contexts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix static file picker to show error state for missing files Update umb-input-static-file to observe statuses and render based on item state (loading, error, success). When a static file is missing (API returns empty array), displays error state with alert icon and file path detail using umb-entity-item-ref. Also adds standalone property support for proper single-item styling. Fixes #19329 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Show file path in static file remove confirmation dialog Override requestRemoveItem in UmbStaticFilePickerInputContext to display the file path instead of "Not found" in the confirmation dialog when removing missing static files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Show GUID in document picker error state Display the document GUID as errorDetail when a document is not found (deleted/gone). This provides useful context for editors to identify which document was referenced. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Show GUID in document picker remove confirmation dialog Display the document GUID instead of "Not found" in the remove confirmation dialog when the document no longer exists. This provides useful context for editors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: apply the temp model which the context uses * Refactor: Move requestRemoveItem logic to base UmbPickerInputContext Eliminated duplicate code across three picker contexts by: - Adding protected getItemDisplayName() method to base class - Moving requestRemoveItem implementation to base class - Removing duplicate implementations from document, member, and static file pickers - Static file picker overrides getItemDisplayName() to show file path Net reduction: 19 lines of code (69 removed, 50 added) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Document Type Picker: Show error state for missing items (fixes #20367) Apply the same error state handling to the document type picker that was implemented for static files, documents, and members. When a referenced document type is missing or deleted: - Show error state with the GUID as errorDetail - Allow removal with proper confirmation dialog - Use umb-entity-item-ref for error display - Use uui-ref-node-document-type for successful items 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Additional pickers: Show error states for missing items in user, language, media-type, member-type, member-group, and user-group pickers Apply the same error state handling pattern to six additional picker types: - user-input: Users - input-language: Languages - input-media-type: Media types - input-member-type: Member types - input-member-group: Member groups - user-group-input: User groups All pickers now: - Observe statuses from UmbRepositoryItemsManager - Show error state with GUID when referenced item is missing/deleted - Use umb-entity-item-ref for error display - Use specialized components (uui-ref-node, umb-user-group-ref, etc.) for successful items - Allow removal with proper confirmation dialog showing GUID Maintains code reusability by using the base class requestRemoveItem method with getItemDisplayName() for consistent error handling across all pickers. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Lint: Remove unused 'when' imports from input-media-type and user-group-input * Refactor: Add #renderItem helper method to all pickers for consistency - Add #renderItem to user-input (extracted from inline repeat callback) - Change _renderItem to #renderItem in user-group-input for consistency - Change _renderItem to #renderItem in input-static-file for consistency All 10 pickers now use consistent #renderItem helper method pattern, improving code readability and maintainability as suggested by @nielslyngsoe * `import` sorting * Corrected (old) JSDoc typos * Markup tidy-up * exported `UmbPropertyEditorUIStaticFilePickerElement` as `element` --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: leekelleher <leekelleher@gmail.com>
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
import type { ManifestEntityItemRef } from './entity-item-ref.extension.js';
|
||||
import { customElement, property, type PropertyValueMap, state, css, html } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbExtensionsElementInitializer } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { UMB_MARK_ATTRIBUTE_NAME } from '@umbraco-cms/backoffice/const';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbDeselectedEvent, UmbSelectedEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbRoutePathAddendumContext } from '@umbraco-cms/backoffice/router';
|
||||
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
|
||||
import { UMB_MARK_ATTRIBUTE_NAME } from '@umbraco-cms/backoffice/const';
|
||||
import { UUIBlinkAnimationValue } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
|
||||
|
||||
import './default-item-ref.element.js';
|
||||
import { UmbDeselectedEvent, UmbSelectedEvent } from '@umbraco-cms/backoffice/event';
|
||||
|
||||
@customElement('umb-entity-item-ref')
|
||||
export class UmbEntityItemRefElement extends UmbLitElement {
|
||||
@@ -20,9 +21,6 @@ export class UmbEntityItemRefElement extends UmbLitElement {
|
||||
private _component?: any; // TODO: Add type
|
||||
|
||||
@property({ type: Object, attribute: false })
|
||||
public get item(): UmbEntityModel | undefined {
|
||||
return this.#item;
|
||||
}
|
||||
public set item(value: UmbEntityModel | undefined) {
|
||||
const oldValue = this.#item;
|
||||
this.#item = value;
|
||||
@@ -41,6 +39,9 @@ export class UmbEntityItemRefElement extends UmbLitElement {
|
||||
// If the component is already created, but the entity type is different, we need to destroy the component.
|
||||
this.#createController(value.entityType);
|
||||
}
|
||||
public get item(): UmbEntityModel | undefined {
|
||||
return this.#item;
|
||||
}
|
||||
|
||||
#readonly = false;
|
||||
@property({ type: Boolean, reflect: true })
|
||||
@@ -124,20 +125,23 @@ export class UmbEntityItemRefElement extends UmbLitElement {
|
||||
error?: boolean;
|
||||
|
||||
@property({ type: String, attribute: 'error-message', reflect: false })
|
||||
errorMessage?: string;
|
||||
errorMessage?: string | null;
|
||||
|
||||
@property({ type: String, attribute: 'error-detail', reflect: false })
|
||||
errorDetail?: string | null;
|
||||
|
||||
#pathAddendum = new UmbRoutePathAddendumContext(this);
|
||||
|
||||
#onSelected(event: UmbSelectedEvent) {
|
||||
event.stopPropagation();
|
||||
const unique = this.#item?.unique;
|
||||
const unique = this.item?.unique;
|
||||
if (!unique) throw new Error('No unique id found for item');
|
||||
this.dispatchEvent(new UmbSelectedEvent(unique));
|
||||
}
|
||||
|
||||
#onDeselected(event: UmbDeselectedEvent) {
|
||||
event.stopPropagation();
|
||||
const unique = this.#item?.unique;
|
||||
const unique = this.item?.unique;
|
||||
if (!unique) throw new Error('No unique id found for item');
|
||||
this.dispatchEvent(new UmbDeselectedEvent(unique));
|
||||
}
|
||||
@@ -163,7 +167,7 @@ export class UmbEntityItemRefElement extends UmbLitElement {
|
||||
|
||||
// TODO: I would say this code can use feature of the UmbExtensionsElementInitializer, to set properties and get a fallback element. [NL]
|
||||
// assign the properties to the component
|
||||
component.item = this.#item;
|
||||
component.item = this.item;
|
||||
component.readonly = this.readonly;
|
||||
component.standalone = this.standalone;
|
||||
component.selectOnly = this.selectOnly;
|
||||
@@ -192,20 +196,25 @@ export class UmbEntityItemRefElement extends UmbLitElement {
|
||||
if (this._component) {
|
||||
return html`${this._component}`;
|
||||
}
|
||||
|
||||
// Error:
|
||||
if (this.error) {
|
||||
return html`<uui-ref-node
|
||||
style="color: var(--uui-color-danger);"
|
||||
.name=${this.localize.string(this.errorMessage ?? '#general_notFound')}
|
||||
.readonly=${this.readonly}
|
||||
.standalone=${this.standalone}
|
||||
.selectOnly=${this.selectOnly}
|
||||
.selected=${this.selected}
|
||||
.disabled=${this.disabled}>
|
||||
<uui-icon slot="icon" name="icon-alert" style="color: var(--uui-color-danger);"></uui-icon>
|
||||
<slot name="actions"></slot>
|
||||
</uui-ref-node>`;
|
||||
return html`
|
||||
<uui-ref-node
|
||||
style="color: var(--uui-color-danger);"
|
||||
.name=${this.localize.string(this.errorMessage ?? '#general_notFound')}
|
||||
.detail=${this.errorDetail ?? ''}
|
||||
.readonly=${this.readonly}
|
||||
.standalone=${this.standalone}
|
||||
.selectOnly=${this.selectOnly}
|
||||
.selected=${this.selected}
|
||||
.disabled=${this.disabled}>
|
||||
<uui-icon slot="icon" name="icon-alert" style="color: var(--uui-color-danger);"></uui-icon>
|
||||
<slot name="actions"></slot>
|
||||
</uui-ref-node>
|
||||
`;
|
||||
}
|
||||
|
||||
// Loading:
|
||||
return html`<uui-loader-bar style="margin-top:10px;"></uui-loader-bar>`;
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ export class UmbPickerInputContext<
|
||||
public readonly interactionMemory = new UmbInteractionMemoryManager(this);
|
||||
|
||||
/**
|
||||
* Define a minimum amount of selected items in this input, for this input to be valid.
|
||||
* @returns {number} The minimum number of items required.
|
||||
* Define a maximum amount of selected items in this input, for this input to be valid.
|
||||
* @returns {number} The maximum number of items required.
|
||||
*/
|
||||
public get max() {
|
||||
return this._max;
|
||||
@@ -43,7 +43,7 @@ export class UmbPickerInputContext<
|
||||
private _max = Infinity;
|
||||
|
||||
/**
|
||||
* Define a maximum amount of selected items in this input, for this input to be valid.
|
||||
* Define a minimum amount of selected items in this input, for this input to be valid.
|
||||
* @returns {number} The minimum number of items required.
|
||||
*/
|
||||
public get min() {
|
||||
@@ -111,21 +111,32 @@ export class UmbPickerInputContext<
|
||||
this.getHostElement().dispatchEvent(new UmbChangeEvent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the display name for an item to show in the remove confirmation dialog.
|
||||
* Subclasses can override this to provide custom formatting for missing items.
|
||||
* @param item - The item to get the display name for, or undefined if not found
|
||||
* @param unique - The unique identifier of the item
|
||||
* @returns The display name to show in the dialog
|
||||
*/
|
||||
protected getItemDisplayName(item: PickedItemType | undefined, unique: string): string {
|
||||
return item?.name ?? unique;
|
||||
}
|
||||
|
||||
async requestRemoveItem(unique: string) {
|
||||
const item = this.#itemManager.getItems().find((item) => item.unique === unique);
|
||||
const name = this.getItemDisplayName(item, unique);
|
||||
|
||||
const name = item?.name ?? '#general_notFound';
|
||||
await umbConfirmModal(this, {
|
||||
color: 'danger',
|
||||
headline: `#actions_remove ${name}?`,
|
||||
headline: `#actions_remove?`,
|
||||
content: `#defaultdialogs_confirmremove ${name}?`,
|
||||
confirmLabel: '#actions_remove',
|
||||
});
|
||||
|
||||
this.#removeItem(unique);
|
||||
this._removeItem(unique);
|
||||
}
|
||||
|
||||
#removeItem(unique: string) {
|
||||
protected _removeItem(unique: string) {
|
||||
const newSelection = this.getSelection().filter((value) => value !== unique);
|
||||
this.setSelection(newSelection);
|
||||
this.getHostElement().dispatchEvent(new UmbChangeEvent());
|
||||
|
||||
@@ -2,13 +2,16 @@ import type { UmbDocumentTypeItemModel, UmbDocumentTypeTreeItemModel } from '../
|
||||
import { UMB_DOCUMENT_TYPE_WORKSPACE_MODAL } from '../../constants.js';
|
||||
import { UMB_EDIT_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN } from '../../paths.js';
|
||||
import { UmbDocumentTypePickerInputContext } from './input-document-type.context.js';
|
||||
import { css, html, customElement, property, state, repeat, nothing, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, customElement, html, nothing, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
|
||||
import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository';
|
||||
|
||||
import '@umbraco-cms/backoffice/entity-item';
|
||||
|
||||
@customElement('umb-input-document-type')
|
||||
export class UmbInputDocumentTypeElement extends UmbFormControlMixin<string | undefined, typeof UmbLitElement>(
|
||||
@@ -112,6 +115,9 @@ export class UmbInputDocumentTypeElement extends UmbFormControlMixin<string | un
|
||||
@state()
|
||||
private _items?: Array<UmbDocumentTypeItemModel>;
|
||||
|
||||
@state()
|
||||
private _statuses?: Array<UmbRepositoryItemsStatus>;
|
||||
|
||||
@state()
|
||||
private _editPath = '';
|
||||
|
||||
@@ -143,6 +149,7 @@ export class UmbInputDocumentTypeElement extends UmbFormControlMixin<string | un
|
||||
|
||||
this.observe(this.#pickerContext.selection, (selection) => (this.value = selection.join(',')), '_observeSelection');
|
||||
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems), '_observerItems');
|
||||
this.observe(this.#pickerContext.statuses, (statuses) => (this._statuses = statuses), '_observeStatuses');
|
||||
}
|
||||
|
||||
protected override getFormElement() {
|
||||
@@ -151,8 +158,8 @@ export class UmbInputDocumentTypeElement extends UmbFormControlMixin<string | un
|
||||
|
||||
#getPickableFilter() {
|
||||
if (this.documentTypesOnly) {
|
||||
/* TODO: We do not have the same model in the tree and during the search, so theoretically, we cannot use the same filter.
|
||||
The search item model does not include "isFolder," so it checks for falsy intentionally.
|
||||
/* TODO: We do not have the same model in the tree and during the search, so theoretically, we cannot use the same filter.
|
||||
The search item model does not include "isFolder," so it checks for falsy intentionally.
|
||||
We need to investigate getting this typed correctly. [MR] */
|
||||
return (x: UmbDocumentTypeTreeItemModel) => !x.isFolder && x.isElement === false;
|
||||
}
|
||||
@@ -184,8 +191,8 @@ export class UmbInputDocumentTypeElement extends UmbFormControlMixin<string | un
|
||||
);
|
||||
}
|
||||
|
||||
#removeItem(item: UmbDocumentTypeItemModel) {
|
||||
this.#pickerContext.requestRemoveItem(item.unique);
|
||||
#removeItem(unique: string) {
|
||||
this.#pickerContext.requestRemoveItem(unique);
|
||||
}
|
||||
|
||||
override render() {
|
||||
@@ -204,38 +211,66 @@ export class UmbInputDocumentTypeElement extends UmbFormControlMixin<string | un
|
||||
}
|
||||
|
||||
#renderItems() {
|
||||
if (!this._items) return nothing;
|
||||
if (!this._statuses) return nothing;
|
||||
return html`
|
||||
<uui-ref-list>
|
||||
${repeat(
|
||||
this._items,
|
||||
(item) => item.unique,
|
||||
(item) => this.#renderItem(item),
|
||||
this._statuses,
|
||||
(status) => status.unique,
|
||||
(status) => {
|
||||
const unique = status.unique;
|
||||
const item = this._items?.find((x) => x.unique === unique);
|
||||
const isError = status.state.type === 'error';
|
||||
|
||||
// For error state, use umb-entity-item-ref
|
||||
if (isError) {
|
||||
return html`
|
||||
<umb-entity-item-ref
|
||||
id=${unique}
|
||||
.item=${item}
|
||||
?error=${true}
|
||||
.errorMessage=${status.state.error}
|
||||
.errorDetail=${unique}
|
||||
?readonly=${this.readonly}
|
||||
?standalone=${this.max === 1}>
|
||||
${when(
|
||||
!this.readonly,
|
||||
() => html`
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button
|
||||
label=${this.localize.term('general_remove')}
|
||||
@click=${() => this.#removeItem(unique)}></uui-button>
|
||||
</uui-action-bar>
|
||||
`,
|
||||
)}
|
||||
</umb-entity-item-ref>
|
||||
`;
|
||||
}
|
||||
|
||||
// For successful items, use the document type specific component
|
||||
if (!item) return nothing;
|
||||
const href = this._editPath + UMB_EDIT_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN.generateLocal({ unique });
|
||||
return html`
|
||||
<uui-ref-node-document-type id=${unique} name=${this.localize.string(item.name)} href=${href}>
|
||||
${this.#renderIcon(item)}
|
||||
<uui-action-bar slot="actions">
|
||||
${when(
|
||||
!this.readonly,
|
||||
() => html`
|
||||
<uui-button
|
||||
label=${this.localize.term('general_remove')}
|
||||
@click=${() => this.#removeItem(unique)}></uui-button>
|
||||
`,
|
||||
)}
|
||||
</uui-action-bar>
|
||||
</uui-ref-node-document-type>
|
||||
`;
|
||||
},
|
||||
)}
|
||||
</uui-ref-list>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderItem(item: UmbDocumentTypeItemModel) {
|
||||
if (!item.unique) return;
|
||||
const href = this._editPath + UMB_EDIT_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN.generateLocal({ unique: item.unique });
|
||||
return html`
|
||||
<uui-ref-node-document-type id=${item.unique} name=${this.localize.string(item.name)} href=${href}>
|
||||
${this.#renderIcon(item)}
|
||||
<uui-action-bar slot="actions">
|
||||
${when(
|
||||
!this.readonly,
|
||||
() => html`
|
||||
<uui-button
|
||||
label=${this.localize.term('general_remove')}
|
||||
@click=${() => this.#removeItem(item)}></uui-button>
|
||||
`,
|
||||
)}
|
||||
</uui-action-bar>
|
||||
</uui-ref-node-document-type>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderIcon(item: UmbDocumentTypeItemModel) {
|
||||
if (!item.icon) return;
|
||||
return html`<umb-icon slot="icon" name=${item.icon}></umb-icon>`;
|
||||
|
||||
@@ -239,24 +239,28 @@ export class UmbInputDocumentElement extends UmbFormControlMixin<string | undefi
|
||||
(status) => {
|
||||
const unique = status.unique;
|
||||
const item = this._items?.find((x) => x.unique === unique);
|
||||
return html`<umb-entity-item-ref
|
||||
id=${unique}
|
||||
.item=${item}
|
||||
?error=${status.state.type === 'error'}
|
||||
.errorMessage=${status.state.error}
|
||||
?readonly=${this.readonly}
|
||||
?standalone=${this.max === 1}>
|
||||
${when(
|
||||
!this.readonly,
|
||||
() => html`
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button
|
||||
label=${this.localize.term('general_remove')}
|
||||
@click=${() => this.#onRemove(unique)}></uui-button>
|
||||
</uui-action-bar>
|
||||
`,
|
||||
)}
|
||||
</umb-entity-item-ref>`;
|
||||
const isError = status.state.type === 'error';
|
||||
return html`
|
||||
<umb-entity-item-ref
|
||||
id=${unique}
|
||||
.item=${item}
|
||||
?error=${isError}
|
||||
.errorMessage=${status.state.error}
|
||||
.errorDetail=${isError ? unique : undefined}
|
||||
?readonly=${this.readonly}
|
||||
?standalone=${this.max === 1}>
|
||||
${when(
|
||||
!this.readonly,
|
||||
() => html`
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button
|
||||
label=${this.localize.term('general_remove')}
|
||||
@click=${() => this.#onRemove(unique)}></uui-button>
|
||||
</uui-action-bar>
|
||||
`,
|
||||
)}
|
||||
</umb-entity-item-ref>
|
||||
`;
|
||||
},
|
||||
)}
|
||||
</uui-ref-list>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository';
|
||||
|
||||
@customElement('umb-input-language')
|
||||
export class UmbInputLanguageElement extends UUIFormControlMixin(UmbLitElement, '') {
|
||||
@@ -17,7 +18,7 @@ export class UmbInputLanguageElement extends UUIFormControlMixin(UmbLitElement,
|
||||
return modelEntry;
|
||||
},
|
||||
identifier: 'Umb.SorterIdentifier.InputLanguage',
|
||||
itemSelector: 'uui-ref-node',
|
||||
itemSelector: 'umb-entity-item-ref',
|
||||
containerSelector: 'uui-ref-list',
|
||||
onChange: ({ model }) => {
|
||||
this.selection = model;
|
||||
@@ -115,6 +116,9 @@ export class UmbInputLanguageElement extends UUIFormControlMixin(UmbLitElement,
|
||||
@state()
|
||||
private _items: Array<UmbLanguageItemModel> = [];
|
||||
|
||||
@state()
|
||||
private _statuses?: Array<UmbRepositoryItemsStatus>;
|
||||
|
||||
#pickerContext = new UmbLanguagePickerInputContext(this);
|
||||
|
||||
constructor() {
|
||||
@@ -134,6 +138,7 @@ export class UmbInputLanguageElement extends UUIFormControlMixin(UmbLitElement,
|
||||
|
||||
this.observe(this.#pickerContext.selection, (selection) => (this.value = selection.join(',')), '_observeSelection');
|
||||
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems), '_observerItems');
|
||||
this.observe(this.#pickerContext.statuses, (statuses) => (this._statuses = statuses), '_observeStatuses');
|
||||
}
|
||||
|
||||
protected override getFormElement() {
|
||||
@@ -147,8 +152,8 @@ export class UmbInputLanguageElement extends UUIFormControlMixin(UmbLitElement,
|
||||
});
|
||||
}
|
||||
|
||||
#onRemove(item: UmbLanguageItemModel) {
|
||||
this.#pickerContext.requestRemoveItem(item.unique);
|
||||
#onRemove(unique: string) {
|
||||
this.#pickerContext.requestRemoveItem(unique);
|
||||
}
|
||||
|
||||
override render() {
|
||||
@@ -167,29 +172,38 @@ export class UmbInputLanguageElement extends UUIFormControlMixin(UmbLitElement,
|
||||
}
|
||||
|
||||
#renderItems() {
|
||||
if (!this._items) return;
|
||||
if (!this._statuses) return;
|
||||
return html`
|
||||
<uui-ref-list>
|
||||
${repeat(
|
||||
this._items,
|
||||
(item) => item.unique,
|
||||
(item) =>
|
||||
html`<umb-entity-item-ref
|
||||
id=${item.unique}
|
||||
.item=${item}
|
||||
?readonly=${this.readonly}
|
||||
?standalone=${this.max === 1}>
|
||||
${when(
|
||||
!this.readonly,
|
||||
() => html`
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button
|
||||
label=${this.localize.term('general_remove')}
|
||||
@click=${() => this.#onRemove(item)}></uui-button>
|
||||
</uui-action-bar>
|
||||
`,
|
||||
)}
|
||||
</umb-entity-item-ref>`,
|
||||
this._statuses,
|
||||
(status) => status.unique,
|
||||
(status) => {
|
||||
const unique = status.unique;
|
||||
const item = this._items?.find((x) => x.unique === unique);
|
||||
const isError = status.state.type === 'error';
|
||||
return html`
|
||||
<umb-entity-item-ref
|
||||
id=${unique}
|
||||
.item=${item}
|
||||
?error=${isError}
|
||||
.errorMessage=${status.state.error}
|
||||
.errorDetail=${isError ? unique : undefined}
|
||||
?readonly=${this.readonly}
|
||||
?standalone=${this.max === 1}>
|
||||
${when(
|
||||
!this.readonly,
|
||||
() => html`
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button
|
||||
label=${this.localize.term('general_remove')}
|
||||
@click=${() => this.#onRemove(unique)}></uui-button>
|
||||
</uui-action-bar>
|
||||
`,
|
||||
)}
|
||||
</umb-entity-item-ref>
|
||||
`;
|
||||
},
|
||||
)}
|
||||
</uui-ref-list>
|
||||
`;
|
||||
|
||||
@@ -8,6 +8,9 @@ import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace';
|
||||
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
|
||||
import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository';
|
||||
|
||||
import '@umbraco-cms/backoffice/entity-item';
|
||||
|
||||
@customElement('umb-input-media-type')
|
||||
export class UmbInputMediaTypeElement extends UmbFormControlMixin<string | undefined, typeof UmbLitElement>(
|
||||
@@ -95,6 +98,9 @@ export class UmbInputMediaTypeElement extends UmbFormControlMixin<string | undef
|
||||
@state()
|
||||
private _items?: Array<UmbMediaTypeItemModel>;
|
||||
|
||||
@state()
|
||||
private _statuses?: Array<UmbRepositoryItemsStatus>;
|
||||
|
||||
@state()
|
||||
private _editPath = '';
|
||||
|
||||
@@ -126,6 +132,7 @@ export class UmbInputMediaTypeElement extends UmbFormControlMixin<string | undef
|
||||
|
||||
this.observe(this.#pickerContext.selection, (selection) => (this.value = selection.join(',')), '_observeSelection');
|
||||
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems), '_observerItems');
|
||||
this.observe(this.#pickerContext.statuses, (statuses) => (this._statuses = statuses), '_observeStatuses');
|
||||
}
|
||||
|
||||
protected override getFormElement() {
|
||||
@@ -138,8 +145,8 @@ export class UmbInputMediaTypeElement extends UmbFormControlMixin<string | undef
|
||||
});
|
||||
}
|
||||
|
||||
#removeItem(item: UmbMediaTypeItemModel) {
|
||||
this.#pickerContext.requestRemoveItem(item.unique);
|
||||
#removeItem(unique: string) {
|
||||
this.#pickerContext.requestRemoveItem(unique);
|
||||
}
|
||||
|
||||
override render() {
|
||||
@@ -158,32 +165,56 @@ export class UmbInputMediaTypeElement extends UmbFormControlMixin<string | undef
|
||||
}
|
||||
|
||||
#renderItems() {
|
||||
if (!this._items) return nothing;
|
||||
if (!this._statuses) return nothing;
|
||||
return html`
|
||||
<uui-ref-list>
|
||||
${repeat(
|
||||
this._items,
|
||||
(item) => item.unique,
|
||||
(item) => this.#renderItem(item),
|
||||
this._statuses,
|
||||
(status) => status.unique,
|
||||
(status) => {
|
||||
const unique = status.unique;
|
||||
const item = this._items?.find((x) => x.unique === unique);
|
||||
const isError = status.state.type === 'error';
|
||||
|
||||
// For error state, use umb-entity-item-ref
|
||||
if (isError) {
|
||||
return html`
|
||||
<umb-entity-item-ref
|
||||
id=${unique}
|
||||
.item=${item}
|
||||
?error=${true}
|
||||
.errorMessage=${status.state.error}
|
||||
.errorDetail=${unique}
|
||||
?standalone=${this.max === 1}>
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button
|
||||
label=${this.localize.term('general_remove')}
|
||||
@click=${() => this.#removeItem(unique)}></uui-button>
|
||||
</uui-action-bar>
|
||||
</umb-entity-item-ref>
|
||||
`;
|
||||
}
|
||||
|
||||
// For successful items, use the media type specific component
|
||||
if (!item) return nothing;
|
||||
const href = `${this._editPath}edit/${unique}`;
|
||||
return html`
|
||||
<uui-ref-node-document-type name=${this.localize.string(item.name)} id=${unique}>
|
||||
${this.#renderIcon(item)}
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button href=${href} label=${this.localize.term('general_open')}></uui-button>
|
||||
<uui-button
|
||||
label=${this.localize.term('general_remove')}
|
||||
@click=${() => this.#removeItem(unique)}></uui-button>
|
||||
</uui-action-bar>
|
||||
</uui-ref-node-document-type>
|
||||
`;
|
||||
},
|
||||
)}
|
||||
</uui-ref-list>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderItem(item: UmbMediaTypeItemModel) {
|
||||
if (!item.unique) return;
|
||||
const href = `${this._editPath}edit/${item.unique}`;
|
||||
return html`
|
||||
<uui-ref-node-document-type name=${this.localize.string(item.name)} id=${item.unique}>
|
||||
${this.#renderIcon(item)}
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button href=${href} label=${this.localize.term('general_open')}></uui-button>
|
||||
<uui-button @click=${() => this.#removeItem(item)} label=${this.localize.term('general_remove')}></uui-button>
|
||||
</uui-action-bar>
|
||||
</uui-ref-node-document-type>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderIcon(item: UmbMediaTypeItemModel) {
|
||||
if (!item.icon) return;
|
||||
return html`<umb-icon slot="icon" name=${item.icon}></umb-icon>`;
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import type { UmbMemberGroupItemModel } from '../../types.js';
|
||||
import { UmbMemberGroupPickerInputContext } from './input-member-group.context.js';
|
||||
import { css, html, customElement, property, state, repeat, nothing } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, customElement, html, nothing, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace';
|
||||
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
|
||||
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
|
||||
import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository';
|
||||
|
||||
import '@umbraco-cms/backoffice/entity-item';
|
||||
|
||||
@customElement('umb-input-member-group')
|
||||
export class UmbInputMemberGroupElement extends UmbFormControlMixin<string | undefined, typeof UmbLitElement>(
|
||||
@@ -124,6 +127,9 @@ export class UmbInputMemberGroupElement extends UmbFormControlMixin<string | und
|
||||
@state()
|
||||
private _items?: Array<UmbMemberGroupItemModel>;
|
||||
|
||||
@state()
|
||||
private _statuses?: Array<UmbRepositoryItemsStatus>;
|
||||
|
||||
#pickerContext = new UmbMemberGroupPickerInputContext(this);
|
||||
|
||||
constructor() {
|
||||
@@ -152,6 +158,7 @@ export class UmbInputMemberGroupElement extends UmbFormControlMixin<string | und
|
||||
|
||||
this.observe(this.#pickerContext.selection, (selection) => (this.value = selection.join(',')), '_observeSelection');
|
||||
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems), '_observeItems');
|
||||
this.observe(this.#pickerContext.statuses, (statuses) => (this._statuses = statuses), '_observeStatuses');
|
||||
}
|
||||
|
||||
protected override getFormElement() {
|
||||
@@ -164,8 +171,8 @@ export class UmbInputMemberGroupElement extends UmbFormControlMixin<string | und
|
||||
});
|
||||
}
|
||||
|
||||
#removeItem(item: UmbMemberGroupItemModel) {
|
||||
this.#pickerContext.requestRemoveItem(item.unique);
|
||||
#removeItem(unique: string) {
|
||||
this.#pickerContext.requestRemoveItem(unique);
|
||||
}
|
||||
|
||||
override render() {
|
||||
@@ -173,13 +180,62 @@ export class UmbInputMemberGroupElement extends UmbFormControlMixin<string | und
|
||||
}
|
||||
|
||||
#renderItems() {
|
||||
if (!this._items) return;
|
||||
if (!this._statuses) return;
|
||||
return html`
|
||||
<uui-ref-list>
|
||||
${repeat(
|
||||
this._items,
|
||||
(item) => item.unique,
|
||||
(item) => this.#renderItem(item),
|
||||
this._statuses,
|
||||
(status) => status.unique,
|
||||
(status) => {
|
||||
const unique = status.unique;
|
||||
const item = this._items?.find((x) => x.unique === unique);
|
||||
const isError = status.state.type === 'error';
|
||||
|
||||
// For error state, use umb-entity-item-ref
|
||||
if (isError) {
|
||||
return html`
|
||||
<umb-entity-item-ref
|
||||
id=${unique}
|
||||
?error=${true}
|
||||
.errorMessage=${status.state.error}
|
||||
.errorDetail=${unique}
|
||||
?readonly=${this.readonly}
|
||||
?standalone=${this.max === 1}>
|
||||
${when(
|
||||
!this.readonly,
|
||||
() => html`
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button
|
||||
label=${this.localize.term('general_remove')}
|
||||
@click=${() => this.#removeItem(unique)}></uui-button>
|
||||
</uui-action-bar>
|
||||
`,
|
||||
)}
|
||||
</umb-entity-item-ref>
|
||||
`;
|
||||
}
|
||||
|
||||
// For successful items, use uui-ref-node
|
||||
if (!item) return nothing;
|
||||
return html`
|
||||
<uui-ref-node
|
||||
name=${item.name}
|
||||
id=${unique}
|
||||
href="${this._editMemberGroupPath}edit/${unique}"
|
||||
?readonly=${this.readonly}>
|
||||
<uui-action-bar slot="actions">
|
||||
${when(
|
||||
!this.readonly,
|
||||
() =>
|
||||
html`<uui-button
|
||||
@click=${() => this.#removeItem(unique)}
|
||||
label=${this.localize.term('general_remove')}></uui-button>`,
|
||||
)}
|
||||
</uui-action-bar>
|
||||
<umb-icon slot="icon" name="icon-users"></umb-icon>
|
||||
</uui-ref-node>
|
||||
`;
|
||||
},
|
||||
)}
|
||||
</uui-ref-list>
|
||||
`;
|
||||
@@ -199,27 +255,6 @@ export class UmbInputMemberGroupElement extends UmbFormControlMixin<string | und
|
||||
}
|
||||
}
|
||||
|
||||
#renderItem(item: UmbMemberGroupItemModel) {
|
||||
if (!item.unique) return nothing;
|
||||
return html`
|
||||
<uui-ref-node
|
||||
name=${item.name}
|
||||
id=${item.unique}
|
||||
href="${this._editMemberGroupPath}edit/${item.unique}"
|
||||
?readonly=${this.readonly}>
|
||||
<uui-action-bar slot="actions"> ${this.#renderRemoveButton(item)} </uui-action-bar>
|
||||
<umb-icon slot="icon" name="icon-users"></umb-icon>
|
||||
</uui-ref-node>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderRemoveButton(item: UmbMemberGroupItemModel) {
|
||||
if (this.readonly) return nothing;
|
||||
return html`<uui-button
|
||||
@click=${() => this.#removeItem(item)}
|
||||
label=${this.localize.term('general_remove')}></uui-button>`;
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
css`
|
||||
#btn-add {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { UmbMemberTypePickerInputContext } from './input-member-type.context.js';
|
||||
import { css, html, customElement, property, state, repeat, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, customElement, html, nothing, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { UmbUniqueItemModel } from '@umbraco-cms/backoffice/models';
|
||||
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository';
|
||||
import type { UmbUniqueItemModel } from '@umbraco-cms/backoffice/models';
|
||||
|
||||
import '@umbraco-cms/backoffice/entity-item';
|
||||
|
||||
@customElement('umb-input-member-type')
|
||||
export class UmbInputMemberTypeElement extends UmbFormControlMixin<string | undefined, typeof UmbLitElement>(
|
||||
@@ -73,6 +76,9 @@ export class UmbInputMemberTypeElement extends UmbFormControlMixin<string | unde
|
||||
@state()
|
||||
private _items?: Array<UmbUniqueItemModel>;
|
||||
|
||||
@state()
|
||||
private _statuses?: Array<UmbRepositoryItemsStatus>;
|
||||
|
||||
#pickerContext = new UmbMemberTypePickerInputContext(this);
|
||||
|
||||
constructor() {
|
||||
@@ -92,6 +98,7 @@ export class UmbInputMemberTypeElement extends UmbFormControlMixin<string | unde
|
||||
|
||||
this.observe(this.#pickerContext.selection, (selection) => (this.value = selection.join(',')));
|
||||
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems));
|
||||
this.observe(this.#pickerContext.statuses, (statuses) => (this._statuses = statuses), '_observeStatuses');
|
||||
}
|
||||
|
||||
protected override getFormElement() {
|
||||
@@ -109,13 +116,13 @@ export class UmbInputMemberTypeElement extends UmbFormControlMixin<string | unde
|
||||
}
|
||||
|
||||
#renderItems() {
|
||||
if (!this._items) return;
|
||||
if (!this._statuses) return;
|
||||
return html`
|
||||
<uui-ref-list>
|
||||
${repeat(
|
||||
this._items,
|
||||
(item) => item.unique,
|
||||
(item) => this.#renderItem(item),
|
||||
this._statuses,
|
||||
(status) => status.unique,
|
||||
(status) => this.#renderItem(status),
|
||||
)}
|
||||
</uui-ref-list>
|
||||
`;
|
||||
@@ -134,14 +141,37 @@ export class UmbInputMemberTypeElement extends UmbFormControlMixin<string | unde
|
||||
`;
|
||||
}
|
||||
|
||||
#renderItem(item: UmbUniqueItemModel) {
|
||||
if (!item.unique) return;
|
||||
#renderItem(status: UmbRepositoryItemsStatus) {
|
||||
const unique = status.unique;
|
||||
const item = this._items?.find((x) => x.unique === unique);
|
||||
const isError = status.state.type === 'error';
|
||||
|
||||
// For error state, use umb-entity-item-ref
|
||||
if (isError) {
|
||||
return html`
|
||||
<umb-entity-item-ref
|
||||
id=${unique}
|
||||
?error=${true}
|
||||
.errorMessage=${status.state.error}
|
||||
.errorDetail=${unique}
|
||||
?standalone=${this.max === 1}>
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button
|
||||
label=${this.localize.term('general_remove')}
|
||||
@click=${() => this.#pickerContext.requestRemoveItem(unique)}></uui-button>
|
||||
</uui-action-bar>
|
||||
</umb-entity-item-ref>
|
||||
`;
|
||||
}
|
||||
|
||||
// For successful items, use the member type specific component
|
||||
if (!item?.unique) return nothing;
|
||||
return html`
|
||||
<uui-ref-node-document-type name=${this.localize.string(item.name)}>
|
||||
${when(item.icon, () => html`<umb-icon slot="icon" name=${item.icon!}></umb-icon>`)}
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button
|
||||
@click=${() => this.#pickerContext.requestRemoveItem(item.unique!)}
|
||||
@click=${() => this.#pickerContext.requestRemoveItem(unique)}
|
||||
label="Remove Member Type ${item.name}"
|
||||
>${this.localize.term('general_remove')}</uui-button
|
||||
>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { UmbMemberItemModel } from '../../item/types.js';
|
||||
import { UmbMemberPickerInputContext } from './input-member.context.js';
|
||||
import { css, customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, customElement, html, nothing, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UMB_MEMBER_TYPE_ENTITY_TYPE } from '@umbraco-cms/backoffice/member-type';
|
||||
import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository';
|
||||
|
||||
@customElement('umb-input-member')
|
||||
export class UmbInputMemberElement extends UmbFormControlMixin<string | undefined, typeof UmbLitElement>(
|
||||
@@ -121,6 +122,9 @@ export class UmbInputMemberElement extends UmbFormControlMixin<string | undefine
|
||||
@state()
|
||||
private _items?: Array<UmbMemberItemModel>;
|
||||
|
||||
@state()
|
||||
private _statuses?: Array<UmbRepositoryItemsStatus>;
|
||||
|
||||
#pickerContext = new UmbMemberPickerInputContext(this);
|
||||
|
||||
constructor() {
|
||||
@@ -140,6 +144,7 @@ export class UmbInputMemberElement extends UmbFormControlMixin<string | undefine
|
||||
|
||||
this.observe(this.#pickerContext.selection, (selection) => (this.value = selection.join(',')), '_observeSelection');
|
||||
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems), '_observeItems');
|
||||
this.observe(this.#pickerContext.statuses, (statuses) => (this._statuses = statuses), '_observeStatuses');
|
||||
}
|
||||
|
||||
#openPicker() {
|
||||
@@ -156,8 +161,8 @@ export class UmbInputMemberElement extends UmbFormControlMixin<string | undefine
|
||||
);
|
||||
}
|
||||
|
||||
#onRemove(item: UmbMemberItemModel) {
|
||||
this.#pickerContext.requestRemoveItem(item.unique);
|
||||
#onRemove(unique: string) {
|
||||
this.#pickerContext.requestRemoveItem(unique);
|
||||
}
|
||||
|
||||
override render() {
|
||||
@@ -165,27 +170,43 @@ export class UmbInputMemberElement extends UmbFormControlMixin<string | undefine
|
||||
}
|
||||
|
||||
#renderItems() {
|
||||
if (!this._items) return nothing;
|
||||
if (!this._statuses) return nothing;
|
||||
return html`
|
||||
<uui-ref-list>
|
||||
${repeat(
|
||||
this._items,
|
||||
(item) => item.unique,
|
||||
(item) => this.#renderItem(item),
|
||||
this._statuses,
|
||||
(status) => status.unique,
|
||||
(status) => {
|
||||
const unique = status.unique;
|
||||
const item = this._items?.find((x) => x.unique === unique);
|
||||
const isError = status.state.type === 'error';
|
||||
return html`
|
||||
<umb-entity-item-ref
|
||||
id=${unique}
|
||||
.item=${item}
|
||||
?error=${isError}
|
||||
.errorMessage=${status.state.error}
|
||||
.errorDetail=${isError ? unique : undefined}
|
||||
?readonly=${this.readonly}
|
||||
?standalone=${this.max === 1}>
|
||||
${when(
|
||||
!this.readonly,
|
||||
() => html`
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button
|
||||
label=${this.localize.term('general_remove')}
|
||||
@click=${() => this.#onRemove(unique)}></uui-button>
|
||||
</uui-action-bar>
|
||||
`,
|
||||
)}
|
||||
</umb-entity-item-ref>
|
||||
`;
|
||||
},
|
||||
)}
|
||||
</uui-ref-list>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderItem(item: UmbMemberItemModel) {
|
||||
if (!item.unique) return nothing;
|
||||
return html`
|
||||
<umb-entity-item-ref id=${item.unique} .item=${item} ?readonly=${this.readonly} ?standalone=${this.max === 1}>
|
||||
<uui-action-bar slot="actions">${this.#renderRemoveButton(item)} </uui-action-bar>
|
||||
</umb-entity-item-ref>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderAddButton() {
|
||||
if (this.selection.length >= this.max) return nothing;
|
||||
if (this.readonly && this.selection.length > 0) {
|
||||
@@ -202,13 +223,6 @@ export class UmbInputMemberElement extends UmbFormControlMixin<string | undefine
|
||||
}
|
||||
}
|
||||
|
||||
#renderRemoveButton(item: UmbMemberItemModel) {
|
||||
if (this.readonly) return nothing;
|
||||
return html`
|
||||
<uui-button @click=${() => this.#onRemove(item)} label=${this.localize.term('general_remove')}></uui-button>
|
||||
`;
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
css`
|
||||
#btn-add {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { UMB_STATIC_FILE_PICKER_MODAL } from '../../modals/index.js';
|
||||
import type { UmbStaticFilePickerModalData, UmbStaticFilePickerModalValue } from '../../modals/index.js';
|
||||
import { UMB_STATIC_FILE_ITEM_REPOSITORY_ALIAS } from '../../constants.js';
|
||||
import type { UmbStaticFilePickerModalData, UmbStaticFilePickerModalValue } from '../../modals/index.js';
|
||||
import type { UmbStaticFileItemModel } from '../../types.js';
|
||||
import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input';
|
||||
import { UmbServerFilePathUniqueSerializer } from '@umbraco-cms/backoffice/server-file-system';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export class UmbStaticFilePickerInputContext extends UmbPickerInputContext<
|
||||
@@ -11,7 +12,14 @@ export class UmbStaticFilePickerInputContext extends UmbPickerInputContext<
|
||||
UmbStaticFilePickerModalData,
|
||||
UmbStaticFilePickerModalValue
|
||||
> {
|
||||
#serializer = new UmbServerFilePathUniqueSerializer();
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host, UMB_STATIC_FILE_ITEM_REPOSITORY_ALIAS, UMB_STATIC_FILE_PICKER_MODAL);
|
||||
}
|
||||
|
||||
protected override getItemDisplayName(item: UmbStaticFileItemModel | undefined, unique: string): string {
|
||||
// If item doesn't exist, use the file path as the name
|
||||
return item?.name ?? this.#serializer.toServerPath(unique) ?? unique;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@ import type { UmbStaticFileItemModel } from '../../repository/item/types.js';
|
||||
import { UmbStaticFilePickerInputContext } from './input-static-file.context.js';
|
||||
import { css, customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbServerFilePathUniqueSerializer } from '@umbraco-cms/backoffice/server-file-system';
|
||||
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
|
||||
import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository';
|
||||
|
||||
import '@umbraco-cms/backoffice/entity-item';
|
||||
|
||||
@customElement('umb-input-static-file')
|
||||
export class UmbInputStaticFileElement extends UmbFormControlMixin<string | undefined, typeof UmbLitElement>(
|
||||
@@ -79,6 +82,9 @@ export class UmbInputStaticFileElement extends UmbFormControlMixin<string | unde
|
||||
@state()
|
||||
private _items?: Array<UmbStaticFileItemModel>;
|
||||
|
||||
@state()
|
||||
private _statuses?: Array<UmbRepositoryItemsStatus>;
|
||||
|
||||
#pickerContext = new UmbStaticFilePickerInputContext(this);
|
||||
|
||||
constructor() {
|
||||
@@ -98,6 +104,7 @@ export class UmbInputStaticFileElement extends UmbFormControlMixin<string | unde
|
||||
|
||||
this.observe(this.#pickerContext.selection, (selection) => (this.value = selection.join(',')));
|
||||
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems));
|
||||
this.observe(this.#pickerContext.statuses, (statuses) => (this._statuses = statuses));
|
||||
}
|
||||
|
||||
protected override getFormElement() {
|
||||
@@ -105,13 +112,13 @@ export class UmbInputStaticFileElement extends UmbFormControlMixin<string | unde
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (!this._items) return nothing;
|
||||
if (!this._statuses) return nothing;
|
||||
return html`
|
||||
<uui-ref-list>
|
||||
${repeat(
|
||||
this._items,
|
||||
(item) => item.unique,
|
||||
(item) => this._renderItem(item),
|
||||
this._statuses,
|
||||
(status) => status.unique,
|
||||
(status) => this.#renderItem(status),
|
||||
)}
|
||||
</uui-ref-list>
|
||||
${this.#renderAddButton()}
|
||||
@@ -137,17 +144,25 @@ export class UmbInputStaticFileElement extends UmbFormControlMixin<string | unde
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderItem(item: UmbStaticFileItemModel) {
|
||||
if (!item.unique) return;
|
||||
#renderItem(status: UmbRepositoryItemsStatus) {
|
||||
const unique = status.unique;
|
||||
const item = this._items?.find((x) => x.unique === unique);
|
||||
const isError = status.state.type === 'error';
|
||||
|
||||
return html`
|
||||
<uui-ref-node name=${item.name} .detail=${this.#serializer.toServerPath(item.unique) || ''}>
|
||||
<!-- TODO: implement is trashed, if we cant retrieve the item on the server (but only ask the server if we need to anyway...). <uui-tag size="s" slot="tag" color="danger">Trashed</uui-tag> -->
|
||||
<umb-entity-item-ref
|
||||
id=${unique}
|
||||
.item=${item}
|
||||
?error=${isError}
|
||||
.errorMessage=${status.state.error}
|
||||
.errorDetail=${isError ? this.#serializer.toServerPath(unique) : undefined}
|
||||
?standalone=${this.max === 1}>
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button
|
||||
label=${this.localize.term('general_remove')}
|
||||
@click=${() => this.#pickerContext.requestRemoveItem(item.unique)}></uui-button>
|
||||
@click=${() => this.#pickerContext.requestRemoveItem(unique)}></uui-button>
|
||||
</uui-action-bar>
|
||||
</uui-ref-node>
|
||||
</umb-entity-item-ref>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ export const manifest: ManifestPropertyEditorUi = {
|
||||
type: 'propertyEditorUi',
|
||||
alias: 'Umb.PropertyEditorUi.StaticFilePicker',
|
||||
name: 'Static File Picker Property Editor UI',
|
||||
js: () => import('./property-editor-ui-static-file-picker.element.js'),
|
||||
element: () => import('./property-editor-ui-static-file-picker.element.js'),
|
||||
meta: {
|
||||
label: 'Static File Picker',
|
||||
icon: 'icon-document',
|
||||
|
||||
@@ -73,6 +73,8 @@ export class UmbPropertyEditorUIStaticFilePickerElement extends UmbLitElement im
|
||||
}
|
||||
}
|
||||
|
||||
export { UmbPropertyEditorUIStaticFilePickerElement as element };
|
||||
|
||||
export default UmbPropertyEditorUIStaticFilePickerElement;
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
import { UMB_USER_GROUP_ENTITY_TYPE } from '../../entity.js';
|
||||
import type { UmbUserGroupItemModel } from '../../repository/index.js';
|
||||
import { UmbUserGroupPickerInputContext } from './user-group-input.context.js';
|
||||
import { css, html, customElement, property, state, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace';
|
||||
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
|
||||
import {
|
||||
css,
|
||||
customElement,
|
||||
html,
|
||||
ifDefined,
|
||||
nothing,
|
||||
property,
|
||||
repeat,
|
||||
state,
|
||||
} from '@umbraco-cms/backoffice/external/lit';
|
||||
import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
|
||||
import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace';
|
||||
import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository';
|
||||
|
||||
import '@umbraco-cms/backoffice/entity-item';
|
||||
|
||||
@customElement('umb-user-group-input')
|
||||
export class UmbUserGroupInputElement extends UUIFormControlMixin(UmbLitElement, '') {
|
||||
@@ -75,6 +87,9 @@ export class UmbUserGroupInputElement extends UUIFormControlMixin(UmbLitElement,
|
||||
@state()
|
||||
private _items?: Array<UmbUserGroupItemModel>;
|
||||
|
||||
@state()
|
||||
private _statuses?: Array<UmbRepositoryItemsStatus>;
|
||||
|
||||
#pickerContext = new UmbUserGroupPickerInputContext(this);
|
||||
|
||||
@state()
|
||||
@@ -97,6 +112,7 @@ export class UmbUserGroupInputElement extends UUIFormControlMixin(UmbLitElement,
|
||||
|
||||
this.observe(this.#pickerContext.selection, (selection) => (this.value = selection.join(',')), '_observeSelection');
|
||||
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems), '_observerItems');
|
||||
this.observe(this.#pickerContext.statuses, (statuses) => (this._statuses = statuses), '_observeStatuses');
|
||||
|
||||
new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL)
|
||||
.addAdditionalPath(UMB_USER_GROUP_ENTITY_TYPE)
|
||||
@@ -114,7 +130,15 @@ export class UmbUserGroupInputElement extends UUIFormControlMixin(UmbLitElement,
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<uui-ref-list>${this._items?.map((item) => this._renderItem(item))}</uui-ref-list>
|
||||
<uui-ref-list>
|
||||
${this._statuses
|
||||
? repeat(
|
||||
this._statuses,
|
||||
(status) => status.unique,
|
||||
(status) => this.#renderItem(status),
|
||||
)
|
||||
: nothing}
|
||||
</uui-ref-list>
|
||||
<uui-button
|
||||
id="btn-add"
|
||||
look="placeholder"
|
||||
@@ -123,15 +147,33 @@ export class UmbUserGroupInputElement extends UUIFormControlMixin(UmbLitElement,
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderItem(item: UmbUserGroupItemModel) {
|
||||
if (!item.unique) return;
|
||||
const href = `${this._editUserGroupPath}edit/${item.unique}`;
|
||||
#renderItem(status: UmbRepositoryItemsStatus) {
|
||||
const unique = status.unique;
|
||||
const item = this._items?.find((x) => x.unique === unique);
|
||||
const isError = status.state.type === 'error';
|
||||
|
||||
// For error state, use umb-entity-item-ref
|
||||
if (isError) {
|
||||
return html`
|
||||
<umb-entity-item-ref id=${unique} ?error=${true} .errorMessage=${status.state.error} .errorDetail=${unique}>
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button
|
||||
label=${this.localize.term('general_remove')}
|
||||
@click=${() => this.#pickerContext.requestRemoveItem(unique)}></uui-button>
|
||||
</uui-action-bar>
|
||||
</umb-entity-item-ref>
|
||||
`;
|
||||
}
|
||||
|
||||
// For successful items, use umb-user-group-ref
|
||||
if (!item?.unique) return nothing;
|
||||
const href = `${this._editUserGroupPath}edit/${unique}`;
|
||||
return html`
|
||||
<umb-user-group-ref name="${ifDefined(item.name)}" href=${href}>
|
||||
${item.icon ? html`<umb-icon slot="icon" name=${item.icon}></umb-icon>` : nothing}
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button
|
||||
@click=${() => this.#pickerContext.requestRemoveItem(item.unique)}
|
||||
@click=${() => this.#pickerContext.requestRemoveItem(unique)}
|
||||
label=${this.localize.term('general_remove')}></uui-button>
|
||||
</uui-action-bar>
|
||||
</umb-user-group-ref>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type { UmbRepositoryItemsStatus } from '@umbraco-cms/backoffice/repository';
|
||||
|
||||
// TODO: Shall we rename to 'umb-input-user'? [LK]
|
||||
@customElement('umb-user-input')
|
||||
@@ -92,6 +93,9 @@ export class UmbUserInputElement extends UUIFormControlMixin(UmbLitElement, '')
|
||||
@state()
|
||||
private _items?: Array<UmbUserItemModel>;
|
||||
|
||||
@state()
|
||||
private _statuses?: Array<UmbRepositoryItemsStatus>;
|
||||
|
||||
#pickerContext = new UmbUserPickerInputContext(this);
|
||||
|
||||
constructor() {
|
||||
@@ -111,6 +115,7 @@ export class UmbUserInputElement extends UUIFormControlMixin(UmbLitElement, '')
|
||||
|
||||
this.observe(this.#pickerContext.selection, (selection) => (this.value = selection.join(',')), '_observeSelection');
|
||||
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems), '_observerItems');
|
||||
this.observe(this.#pickerContext.statuses, (statuses) => (this._statuses = statuses), '_observeStatuses');
|
||||
}
|
||||
|
||||
protected override getFormElement() {
|
||||
@@ -121,8 +126,8 @@ export class UmbUserInputElement extends UUIFormControlMixin(UmbLitElement, '')
|
||||
this.#pickerContext.openPicker({});
|
||||
}
|
||||
|
||||
#removeItem(item: UmbUserItemModel) {
|
||||
this.#pickerContext.requestRemoveItem(item.unique);
|
||||
#removeItem(unique: string) {
|
||||
this.#pickerContext.requestRemoveItem(unique);
|
||||
}
|
||||
|
||||
override render() {
|
||||
@@ -141,24 +146,34 @@ export class UmbUserInputElement extends UUIFormControlMixin(UmbLitElement, '')
|
||||
}
|
||||
|
||||
#renderItems() {
|
||||
if (!this._items) return nothing;
|
||||
if (!this._statuses) return nothing;
|
||||
return html`
|
||||
<uui-ref-list>
|
||||
${repeat(
|
||||
this._items,
|
||||
(item) => item.unique,
|
||||
(item) => this.#renderItem(item),
|
||||
this._statuses,
|
||||
(status) => status.unique,
|
||||
(status) => this.#renderItem(status),
|
||||
)}
|
||||
</uui-ref-list>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderItem(item: UmbUserItemModel) {
|
||||
if (!item.unique) return nothing;
|
||||
#renderItem(status: UmbRepositoryItemsStatus) {
|
||||
const unique = status.unique;
|
||||
const item = this._items?.find((x) => x.unique === unique);
|
||||
const isError = status.state.type === 'error';
|
||||
return html`
|
||||
<umb-entity-item-ref .item=${item} id=${item.unique} ?standalone=${this.max === 1}>
|
||||
<umb-entity-item-ref
|
||||
id=${unique}
|
||||
.item=${item}
|
||||
?error=${isError}
|
||||
.errorMessage=${status.state.error}
|
||||
.errorDetail=${isError ? unique : undefined}
|
||||
?standalone=${this.max === 1}>
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button label=${this.localize.term('general_remove')} @click=${() => this.#removeItem(item)}></uui-button>
|
||||
<uui-button
|
||||
label=${this.localize.term('general_remove')}
|
||||
@click=${() => this.#removeItem(unique)}></uui-button>
|
||||
</uui-action-bar>
|
||||
</umb-entity-item-ref>
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user