Skip to content
Open
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import './menu/collection-menu.element.js';

import './item/global-components.js';
export * from './menu/collection-menu.element.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { UmbCollectionItemModel } from '../types.js';
import { getItemFallbackName, getItemFallbackIcon } from '@umbraco-cms/backoffice/entity-item';
import { UmbDeselectedEvent, UmbSelectedEvent } from '@umbraco-cms/backoffice/event';
import { customElement, html, nothing, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';

@customElement('umb-default-collection-item-card')
export class UmbDefaultCollectionItemCardElement extends UmbLitElement {
@property({ type: Object })
item?: UmbCollectionItemModel;

@property({ type: Boolean })
selectable = false;

@property({ type: Boolean })
selected = false;

@property({ type: Boolean })
selectOnly = false;

@property({ type: Boolean })
disabled = false;

@property({ type: String })
href?: string;

#onSelected(event: CustomEvent) {
if (!this.item) return;
event.stopPropagation();
this.dispatchEvent(new UmbSelectedEvent(this.item.unique));
}

#onDeselected(event: CustomEvent) {
if (!this.item) return;
event.stopPropagation();
this.dispatchEvent(new UmbDeselectedEvent(this.item.unique));
}

override render() {
if (!this.item) return nothing;

return html`
<uui-card-content-node
name=${this.item.name ?? `${getItemFallbackName(this.item)}`}
href=${this.href}
?selectable=${this.selectable}
?select-only=${this.selectOnly}
?selected=${this.selected}
?disabled=${this.disabled}
@selected=${this.#onSelected}
@deselected=${this.#onDeselected}>
<slot name="actions" slot="actions"></slot>
${this.#renderIcon(this.item)}
</uui-card-content-node>
`;
}

#renderIcon(item: UmbCollectionItemModel) {
const icon = item.icon || getItemFallbackIcon();
return html`<umb-icon slot="icon" name=${icon}></umb-icon>`;
}
}

declare global {
interface HTMLElementTagNameMap {
'umb-default-collection-item-card': UmbDefaultCollectionItemCardElement;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import type { UmbCollectionItemDetailPropertyConfig, UmbCollectionItemModel } from '../types.js';
import type { ManifestEntityCollectionItemCard } from './entity-collection-item-card.extension.js';
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 { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UmbDeselectedEvent, UmbSelectedEvent } from '@umbraco-cms/backoffice/event';
import { UmbRoutePathAddendumContext } from '@umbraco-cms/backoffice/router';
import { UMB_MARK_ATTRIBUTE_NAME } from '@umbraco-cms/backoffice/const';
import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';

import './default-collection-item-card.element.js';

@customElement('umb-entity-collection-item-card')
export class UmbEntityCollectionItemCardElement extends UmbLitElement {
#extensionsController?: UmbExtensionsElementInitializer<any>;
#item?: UmbCollectionItemModel;

@state()
private _component?: any; // TODO: Add type

@property({ type: Object, attribute: false })
public set item(value: UmbCollectionItemModel | undefined) {
const oldValue = this.#item;
this.#item = value;

if (value === oldValue) return;
if (!value) return;

// If the component is already created and the entity type is the same, we can just update the item.
if (this._component && value.entityType === oldValue?.entityType) {
this._component.item = value;
return;
}

this.#pathAddendum.setAddendum('collection-item-card/' + value.entityType + '/' + value.unique);

// 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(): UmbCollectionItemModel | undefined {
return this.#item;
}

#selectable = false;
@property({ type: Boolean, reflect: true })
public get selectable() {
return this.#selectable;
}
public set selectable(value) {
this.#selectable = value;

if (this._component) {
this._component.selectable = this.#selectable;
}
}

#selectOnly = false;
@property({ type: Boolean, attribute: 'select-only', reflect: true })
public get selectOnly() {
return this.#selectOnly;
}
public set selectOnly(value) {
this.#selectOnly = value;

if (this._component) {
this._component.selectOnly = this.#selectOnly;
}
}

#selected = false;
@property({ type: Boolean, reflect: true })
public get selected() {
return this.#selected;
}
public set selected(value) {
this.#selected = value;

if (this._component) {
this._component.selected = this.#selected;
}
}

#disabled = false;
@property({ type: Boolean, reflect: true })
public get disabled() {
return this.#disabled;
}
public set disabled(value) {
this.#disabled = value;

if (this._component) {
this._component.disabled = this.#disabled;
}
}

#href?: string;
@property({ type: String, reflect: true })
public get href() {
return this.#href;
}
public set href(value) {
this.#href = value;

if (this._component) {
this._component.href = this.#href;
}
}

#detailProperties?: Array<UmbCollectionItemDetailPropertyConfig>;
@property({ type: Array, attribute: false })
public get detailProperties() {
return this.#detailProperties;
}
public set detailProperties(value) {
this.#detailProperties = value;

if (this._component) {
this._component.detailProperties = this.#detailProperties;
}
}

#pathAddendum = new UmbRoutePathAddendumContext(this);

#onSelected(event: UmbSelectedEvent) {
event.stopPropagation();
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;
if (!unique) throw new Error('No unique id found for item');
this.dispatchEvent(new UmbDeselectedEvent(unique));
}

protected override firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
super.firstUpdated(_changedProperties);
this.setAttribute(UMB_MARK_ATTRIBUTE_NAME, 'entity-collection-item-card');
}

#boundOnSelected = this.#onSelected.bind(this);
#boundOnDeselected = this.#onDeselected.bind(this);

#createController(entityType: string) {
if (this.#extensionsController) {
this.#extensionsController.destroy();
}

this.#extensionsController = new UmbExtensionsElementInitializer(
this,
umbExtensionsRegistry,
'entityCollectionItemCard',
(manifest: ManifestEntityCollectionItemCard) => manifest.forEntityTypes.includes(entityType),
(extensionControllers) => {
this._component?.remove();
const component =
extensionControllers[0]?.component || document.createElement('umb-default-collection-item-card');

// 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.selectable = this.selectable;
component.selectOnly = this.selectOnly;
component.selected = this.selected;
component.disabled = this.disabled;
component.href = this.href;
component.detailProperties = this.detailProperties;

component.addEventListener(UmbSelectedEvent.TYPE, this.#boundOnSelected);
component.addEventListener(UmbDeselectedEvent.TYPE, this.#boundOnDeselected);

// Proxy the actions slot to the component
const slotElement = document.createElement('slot');
slotElement.name = 'actions';
slotElement.setAttribute('slot', 'actions');
component.appendChild(slotElement);

this._component = component;
},
undefined, // We can leave the alias to undefined, as we destroy this ourselves.
undefined,
{ single: true },
);
}

override render() {
return html`${this._component}`;
}

override destroy(): void {
this._component?.removeEventListener(UmbSelectedEvent.TYPE, this.#boundOnSelected);
this._component?.removeEventListener(UmbDeselectedEvent.TYPE, this.#boundOnDeselected);
super.destroy();
}

static override styles = [
css`
:host {
display: block;
position: relative;
}
`,
];
}

declare global {
interface HTMLElementTagNameMap {
'umb-entity-collection-item-card': UmbEntityCollectionItemCardElement;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { ManifestElement, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api';

export interface ManifestEntityCollectionItemCard<
MetaType extends MetaEntityCollectionItemCard = MetaEntityCollectionItemCard,
> extends ManifestElement<any>,
ManifestWithDynamicConditions<UmbExtensionConditionConfig> {
type: 'entityCollectionItemCard';
meta: MetaType;
forEntityTypes: Array<string>;
}

// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface MetaEntityCollectionItemCard {}

declare global {
interface UmbExtensionManifestMap {
umbManifestEntityCollectionItemCard: ManifestEntityCollectionItemCard;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './entity-collection-item-card.element.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './entity-collection-item-card.element.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type * from './entity-collection-item-card.extension.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import './entity-collection-item-card/global-components.js';
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
export type * from './entity-collection-item-card/types.js';

export interface UmbCollectionItemModel extends UmbEntityModel {
unique: string;
name?: string;
icon?: string;
}

export interface UmbCollectionItemDetailPropertyConfig {
alias: string;
name: string;
isSystem: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ export class UmbEntityItemRefElement extends UmbLitElement {
this.setAttribute(UMB_MARK_ATTRIBUTE_NAME, 'entity-item-ref');
}

#boundOnSelected = this.#onSelected.bind(this);
#boundOnDeselected = this.#onDeselected.bind(this);

#createController(entityType: string) {
if (this.#extensionsController) {
this.#extensionsController.destroy();
Expand All @@ -175,8 +178,8 @@ export class UmbEntityItemRefElement extends UmbLitElement {
component.selected = this.selected;
component.disabled = this.disabled;

component.addEventListener(UmbSelectedEvent.TYPE, this.#onSelected.bind(this));
component.addEventListener(UmbDeselectedEvent.TYPE, this.#onDeselected.bind(this));
component.addEventListener(UmbSelectedEvent.TYPE, this.#boundOnSelected);
component.addEventListener(UmbDeselectedEvent.TYPE, this.#boundOnDeselected);

// Proxy the actions slot to the component
const slotElement = document.createElement('slot');
Expand Down Expand Up @@ -220,8 +223,8 @@ export class UmbEntityItemRefElement extends UmbLitElement {
}

override destroy(): void {
this._component?.removeEventListener(UmbSelectedEvent.TYPE, this.#onSelected.bind(this));
this._component?.removeEventListener(UmbDeselectedEvent.TYPE, this.#onDeselected.bind(this));
this._component?.removeEventListener(UmbSelectedEvent.TYPE, this.#boundOnSelected);
this._component?.removeEventListener(UmbDeselectedEvent.TYPE, this.#boundOnDeselected);
super.destroy();
}

Expand Down
Loading
Loading