diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 29961649a3..da7b4d3dab 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -48,6 +48,8 @@ "./entity-bulk-action": "./dist-cms/packages/core/entity-bulk-action/index.js", "./entity-create-option-action": "./dist-cms/packages/core/entity-create-option-action/index.js", "./entity-item": "./dist-cms/packages/core/entity-item/index.js", + "./entity-sign": "./dist-cms/packages/core/entity-sign/index.js", + "./entity-flag": "./dist-cms/packages/core/entity-flag/index.js", "./entity": "./dist-cms/packages/core/entity/index.js", "./event": "./dist-cms/packages/core/event/index.js", "./extension-registry": "./dist-cms/packages/core/extension-registry/index.js", diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts index 885fcfbbaf..fad3f6064c 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts @@ -849,6 +849,7 @@ export default { details: 'Detaljer', dictionary: 'Ordbog', dimensions: 'Dimensioner', + dividerPosition: (value: string | number) => `Skillevæg ved ${value}%`, discard: 'Kassér', down: 'Ned', download: 'Hent', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/de.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/de.ts index dd5792e263..c183998bfb 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/de.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/de.ts @@ -770,6 +770,7 @@ export default { design: 'Design', dictionary: 'Wörterbuch', dimensions: 'Abmessungen', + dividerPosition: (value: string | number) => `Trenner bei ${value}%`, discard: 'Verwerfen', down: 'nach unten', download: 'Herunterladen', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts index ee5eb1bfb3..9d1074a2d5 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts @@ -532,6 +532,7 @@ export default { anchorLinkPicker: 'Anchor or querystring', anchorInsert: 'Name', closeThisWindow: 'Close this window', + colorSwitcher: 'Color switcher', confirmdelete: (name: string) => `Are you sure you want to delete${name ? ` ${name}` : ''}?`, confirmdeleteNumberOfItems: 'Are you sure you want to delete %0% of %1% items', confirmdisable: 'Are you sure you want to disable', @@ -622,6 +623,7 @@ export default { selectUser: 'Select user', selectUsers: 'Select users', chooseUsers: 'Choose users', + noIcon: 'No icon', noIconsFound: 'No icons were found', noMacroParams: 'There are no parameters for this macro', noMacros: 'There are no macros available to insert', @@ -851,6 +853,7 @@ export default { details: 'Details', dictionary: 'Dictionary', dimensions: 'Dimensions', + dividerPosition: (value: string | number) => `Divider at ${value}%`, discard: 'Discard', document: 'Document', down: 'Down', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/es.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/es.ts index deee492da0..581c8b1a7c 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/es.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/es.ts @@ -286,6 +286,7 @@ export default { anchorInsert: 'Nombre', assignDomain: 'Administrar dominios', closeThisWindow: 'Cerrar esta ventana', + colorSwitcher: 'Selector de color', confirmdelete: 'Estás seguro que quieres borrar', confirmdisable: 'Estás seguro que quieres deshabilitar', confirmlogout: '¿Estás seguro?', @@ -349,6 +350,7 @@ export default { selectLanguages: 'Seleccionar idiomas', selectSections: 'Selecciona secciones', selectUsers: 'Selecciona usuarios', + noIcon: 'Sin icono', noIconsFound: 'No se encontraron iconos', noMacroParams: 'No hay parámetros para esta macro', noMacros: 'No hay macros disponibles para insertar', @@ -481,6 +483,7 @@ export default { design: 'Diseño', dictionary: 'Diccionario', dimensions: 'Dimensiones', + dividerPosition: (value: string | number) => `Divisor en ${value}%`, down: 'Abajo', download: 'Descargar', edit: 'Editar', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/fr.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/fr.ts index 7c7bcf58f5..62ee5fac0d 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/fr.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/fr.ts @@ -673,6 +673,7 @@ export default { design: 'Design', dictionary: 'Dictionnaire', dimensions: 'Dimensions', + dividerPosition: (value: string | number) => `Séparateur à ${value}%`, down: 'Bas', download: 'Télécharger', edit: 'Editer', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/nl.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/nl.ts index 0566edcaf4..87b47076e9 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/nl.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/nl.ts @@ -688,6 +688,7 @@ export default { design: 'Ontwerp', dictionary: 'Woordenboek', dimensions: 'Afmetingen', + dividerPosition: (value: string | number) => `Verdeler op ${value}%`, discard: 'Gooi weg', down: 'Omlaag', download: 'Download', 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 c526149e30..e6aa54ff8c 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 @@ -1,3 +1,4 @@ +import type { UmbEntityFlag } from '@umbraco-cms/backoffice/entity-flag'; import type { UmbPropertyValueData } from '@umbraco-cms/backoffice/property'; import type { UmbEntityVariantModel } from '@umbraco-cms/backoffice/variant'; @@ -32,8 +33,9 @@ export interface UmbContentDetailModel; + flags: Array; } export interface UmbContentLikeDetailModel extends UmbElementDetailModel, - Partial> {} + Partial> {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-validation-path-translator.test.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-validation-path-translator.test.ts index 5f341330df..30b88518a3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-validation-path-translator.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-validation-path-translator.test.ts @@ -35,6 +35,7 @@ describe('UmbValidationPropertyPathTranslationController', () => { }, ], variants: [], + flags: [], }; beforeEach(async () => { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/split-panel/split-panel.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/split-panel/split-panel.element.ts index babc299170..d61f105b24 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/split-panel/split-panel.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/split-panel/split-panel.element.ts @@ -1,6 +1,6 @@ +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { type PropertyValueMap, - LitElement, css, customElement, html, @@ -23,7 +23,7 @@ import { clamp } from '@umbraco-cms/backoffice/utils'; * @cssprop --umb-split-panel-divider-color - Color of the divider. */ @customElement('umb-split-panel') -export class UmbSplitPanelElement extends LitElement { +export class UmbSplitPanelElement extends UmbLitElement { @query('#main') mainElement!: HTMLElement; @query('#divider-touch-area') dividerTouchAreaElement!: HTMLElement; @query('#divider') dividerElement!: HTMLElement; @@ -91,11 +91,17 @@ export class UmbSplitPanelElement extends LitElement { } #setPosition(pos: number) { - const { width } = this.mainElement.getBoundingClientRect(); - const localPos = clamp(pos, 0, width); - const percentagePos = (localPos / width) * 100; - this.position = percentagePos + '%'; - } + const { width } = this.mainElement.getBoundingClientRect(); + const localPos = clamp(pos, 0, width); + const percentagePos = (localPos / width) * 100; + this.position = percentagePos + '%'; + + // Update ARIA value for divider + const formatted = percentagePos.toFixed(0); + const ariaText = this.localize?.term('general_dividerPosition', [formatted]) ?? `Divider at ${formatted}%`; + + this.dividerTouchAreaElement.setAttribute('aria-valuetext', ariaText); +} #updateSplit() { // If lock is none diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-flag/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-flag/index.ts new file mode 100644 index 0000000000..06c33f562f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-flag/index.ts @@ -0,0 +1 @@ +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-flag/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-flag/types.ts new file mode 100644 index 0000000000..6db268a91f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-flag/types.ts @@ -0,0 +1,15 @@ +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; + +// Omitting the unique because we don't need it for flags and its causes trouble because it can be both null and a string +export interface UmbEntityWithFlags extends Omit { + flags: Array; +} + +// Omitting the unique because we don't need it for flags and its causes trouble because it can be both null and a string +export interface UmbEntityWithOptionalFlags extends Omit { + flags?: UmbEntityWithFlags['flags']; +} + +export interface UmbEntityFlag { + alias: string; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/components/entity-sign-bundle.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/components/entity-sign-bundle.element.ts new file mode 100644 index 0000000000..731e8ba923 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/components/entity-sign-bundle.element.ts @@ -0,0 +1,277 @@ +import { UmbLitElement } from '../../lit-element/lit-element.element.js'; +import type { ManifestEntitySign } from '../types.js'; +import { customElement, html, nothing, property, repeat, state, css } from '@umbraco-cms/backoffice/external/lit'; +import { UmbExtensionsElementAndApiInitializer } from '@umbraco-cms/backoffice/extension-api'; +import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import type { UmbEntityFlag } from '@umbraco-cms/backoffice/entity-flag'; + +@customElement('umb-entity-sign-bundle') +export class UmbEntitySignBundleElement extends UmbLitElement { + #entityType?: string; + #entityFlagAliases?: Array; + + @property({ type: String, attribute: 'entity-type', reflect: false }) + get entityType(): string | undefined { + return this.#entityType; + } + + set entityType(value: string | undefined) { + if (this.#entityType === value) return; + this.#entityType = value; + this.#gotProperties(); + } + + @property({ type: Array, attribute: false }) + get entityFlags(): Array | undefined { + return this.#entityFlagAliases?.map((x) => ({ alias: x })); + } + + set entityFlags(value: Array | undefined) { + const entityFlagAliases = value?.map((x) => x.alias); + // If they are equal return: + if (this.#entityFlagAliases?.join(',') === entityFlagAliases?.join(',')) return; + this.#entityFlagAliases = entityFlagAliases; + this.#gotProperties(); + } + + @state() + private _signs?: Array; + + @state() + private _labels: Map = new Map(); + + private _open = false; + private _hoverTimer?: number; + + #signLabelObservations: Array> = []; + + constructor() { + super(); + this.addEventListener('mouseenter', this.#openTooltip); + this.addEventListener('mouseleave', this.#cancelOpen); + } + + #manifestFilter = (manifest: ManifestEntitySign) => { + if (manifest.forEntityTypes && !manifest.forEntityTypes.includes(this.#entityType!)) return false; + if (manifest.forEntityFlags && !manifest.forEntityFlags.some((x) => this.#entityFlagAliases?.includes(x))) + return false; + return true; + }; + + #gotProperties() { + if (!this.#entityType || !this.#entityFlagAliases) { + this.removeUmbControllerByAlias('extensionsInitializer'); + this._signs = []; + return; + } + + new UmbExtensionsElementAndApiInitializer( + this, + umbExtensionsRegistry, + 'entitySign', + (manifest: ManifestEntitySign) => [{ meta: manifest.meta }], + this.#manifestFilter, + (signs) => { + // Clean up old observers + this.#signLabelObservations.forEach((o) => this.removeUmbController(o)); + this.#signLabelObservations = []; + + // Setup label observers + signs.forEach((sign) => { + if (sign.api?.label) { + const obs = this.observe( + sign.api.label, + (label) => { + this._labels.set(sign.alias, label); + this.requestUpdate('_labels'); + }, + '_observeSignLabelOf_' + sign.alias, + ); + this.#signLabelObservations.push(obs); + } else if (sign.api?.getLabel) { + this._labels.set(sign.alias, sign.api.getLabel() ?? ''); + this.requestUpdate('_labels'); + } + }); + + this._signs = signs; + }, + 'extensionsInitializer', + ); + } + + #handleHoverTimer(open: boolean, delay: number) { + if (this._hoverTimer) clearTimeout(this._hoverTimer); + this._hoverTimer = window.setTimeout(() => { + this._open = open; + this.requestUpdate(); + this._hoverTimer = undefined; + }, delay); + } + + #openTooltip = () => { + if (!this._open) { + this.#handleHoverTimer(true, 240); + } + }; + + #cancelOpen = () => { + if (this._open) { + this.#handleHoverTimer(false, 360); + } else if (this._hoverTimer) { + clearTimeout(this._hoverTimer); + this._hoverTimer = undefined; + } + }; + + override render() { + return html` + + ${this.#renderBundle()} + `; + } + #renderBundle() { + if (!this._signs || this._signs.length === 0) return nothing; + + const first = this._signs?.[0]; + if (!first) return nothing; + return html`
+ ${this.#renderOptions()} +
`; + } + + #renderOptions() { + return this._signs + ? repeat( + this._signs, + (c) => c.alias, + (c, i) => { + return html`
+ ${c.component}${this._labels.get(c.alias)} +
`; + }, + ) + : nothing; + } + + static override styles = [ + css` + :host { + anchor-name: --entity-sign; + position: relative; + --offset-h: 12px; /* 22px / 16 */ + --row-h: 1.36rem; /* 22px / 16 */ + --icon-w: 0.75rem; /* 12px / 16 */ + --pad-x: 0.25rem; /* 4px / 16 */ + --ease: cubic-bezier(0.1, 0, 0.3, 1); + --ease-bounce: cubic-bezier(0.175, 0.885, 0.32, 1.275); + } + + .infobox { + position: absolute; + top: 100%; + margin-top: calc(-12px + var(--offset-h)); + left: 100%; + margin-left: -6px; + background-color: transparent; + padding: var(--uui-size-2); + padding-left: var(--uui-size-3); + font-size: 8px; + clip-path: inset(-10px calc(100% - 30px) calc(100% - 10px) -20px); + transition: + background-color 80ms 40ms linear, + clip-path 120ms var(--ease-bounce), + font-size 120ms var(--ease); + /*will-change: clip-path;*/ + min-height: fit-content; + } + .infobox::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 100%; + bottom: 100%; + opacity: 0; + border-radius: 3px; + box-shadow: var(--uui-shadow-depth-2); + display: none; + transition: + right 120ms var(--ease-bounce), + bottom 120ms var(--ease-bounce), + opacity 120ms linear, + display 0 120ms; + } + + .infobox > .sign-container { + display: flex; + align-items: start; + gap: 3px; + position: relative; + transform: translate(calc((var(--i) * -5px) - 10px), calc((-1 * var(--i) * var(--row-h)) - var(--offset-h))); + transition: + transform 120ms var(--ease), + visibility 0ms linear 120ms opacity 120ms linear; + z-index: calc(var(--count) - var(--i)); + /*will-change: transform;*/ + pointer-events: none; + } + .infobox > .sign-container.hide-in-overview { + visibility: hidden; + } + + .infobox .sign-container .label { + opacity: 0; + transition: opacity 120ms; + } + + /*OPEN STATE -- Prevent the hover state in firefox(until support of the position-anchor)*/ + @supports (position-anchor: --any-check) { + .infobox { + position: fixed; + position-anchor: --entity-sign; + top: anchor(bottom); + left: anchor(right); + z-index: 1; + } + .infobox.is-open { + z-index: 10; + background-color: var(--uui-color-surface); + font-size: 12px; + color: var(--uui-color-text); + clip-path: inset(-6px); + --umb-sign-bundle-bg: var(--uui-color-surface); + } + .infobox.is-open::before { + right: 0; + bottom: 0; + opacity: 100; + background-color: var(--uui-color-surface); + display: block; + transition: + right 120ms var(--ease-bounce), + bottom 120ms var(--ease-bounce), + opacity 120ms var(--ease), + display 0 0; + } + .infobox.is-open > .sign-container { + transform: none; + align-items: center; + transition: transform 120ms var(--ease); + visibility: visible; + } + .infobox.is-open .sign-container .label { + opacity: 1; + pointer-events: auto; + } + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-entity-sign-bundle': UmbEntitySignBundleElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/components/index.ts new file mode 100644 index 0000000000..e735bf8efe --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/components/index.ts @@ -0,0 +1 @@ +export * from './entity-sign-bundle.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/extensions/entity-sign-api.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/extensions/entity-sign-api.interface.ts new file mode 100644 index 0000000000..b38c8d5566 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/extensions/entity-sign-api.interface.ts @@ -0,0 +1,21 @@ +import type { MetaEntitySign } from './entity-sign.extension.js'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; +import type { Observable } from '@umbraco-cms/backoffice/observable-api'; + +export interface UmbEntitySignApi extends UmbApi { + /** + * Get the label for this sign + * @returns {string} The label + */ + getLabel?: () => string; + + /** + * An observable that provides the label for this sign + * @returns { Observable} A label observable + */ + label?: Observable; +} + +export interface UmbEntitySignApiArgs { + meta: MetaType; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/extensions/entity-sign-element.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/extensions/entity-sign-element.interface.ts new file mode 100644 index 0000000000..43150a6737 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/extensions/entity-sign-element.interface.ts @@ -0,0 +1,6 @@ +import type { ManifestEntitySign } from './entity-sign.extension.js'; +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; + +export interface UmbEntitySignElement extends UmbControllerHostElement { + manifest?: ManifestEntitySign; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/extensions/entity-sign.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/extensions/entity-sign.extension.ts new file mode 100644 index 0000000000..df70c1e91f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/extensions/entity-sign.extension.ts @@ -0,0 +1,25 @@ +import type { UmbEntitySignElement } from './entity-sign-element.interface.js'; +import type { UmbEntitySignApi } from './entity-sign-api.interface.js'; +import type { ManifestElementAndApi, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api'; + +/** + * An action to perform on an entity + * For example for content you may wish to create a new document etc + */ +export interface ManifestEntitySign + extends ManifestElementAndApi, + ManifestWithDynamicConditions { + type: 'entitySign'; + forEntityTypes?: Array; + forEntityFlags?: Array; + meta: MetaType; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface MetaEntitySign {} + +declare global { + interface UmbExtensionManifestMap { + umbEntitySign: ManifestEntitySign; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/extensions/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/extensions/types.ts new file mode 100644 index 0000000000..4810fb2c82 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/extensions/types.ts @@ -0,0 +1,3 @@ +export type * from './entity-sign-element.interface.js'; +export type * from './entity-sign.extension.js'; +export type * from './entity-sign-api.interface.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/index.ts new file mode 100644 index 0000000000..b55ef6b60a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/index.ts @@ -0,0 +1,2 @@ +export * from './components/index.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/entity-sign-icon.api.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/entity-sign-icon.api.ts new file mode 100644 index 0000000000..6f3507904a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/entity-sign-icon.api.ts @@ -0,0 +1,19 @@ +import type { UmbEntitySignApi, UmbEntitySignApiArgs } from '../../extensions/entity-sign-api.interface.js'; +import type { MetaEntitySignIconKind } from './types.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbIconEntitySign implements UmbEntitySignApi { + #label: string; + + constructor(host: UmbControllerHost, args: UmbEntitySignApiArgs) { + this.#label = args.meta.label; + } + + getLabel(): string { + return this.#label; + } + + destroy(): void {} +} + +export { UmbIconEntitySign as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/entity-sign-icon.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/entity-sign-icon.element.ts new file mode 100644 index 0000000000..544ab9c828 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/entity-sign-icon.element.ts @@ -0,0 +1,37 @@ +import type { ManifestEntitySignIconKind } from './types.js'; +import { css, customElement, html, nothing, property } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import type { UmbEntitySignElement } from '../../types.js'; + +@customElement('umb-entity-sign-icon') +export class UmbEntitySignIconElement extends UmbLitElement implements UmbEntitySignElement { + @property({ type: Object, attribute: false }) + manifest?: ManifestEntitySignIconKind; + + override render() { + return this.manifest + ? html`` + : nothing; + } + + static override styles = [ + css` + umb-icon { + filter: drop-shadow(-1px 0 0 var(--umb-sign-bundle-bg)) drop-shadow(0 -1px 0 var(--umb-sign-bundle-bg)) + drop-shadow(0 1px 0 var(--umb-sign-bundle-bg)); + } + umb-icon::before { + content: ''; + position: absolute; + z-index: -1; + border-radius: 50%; + inset: 2px; + background-color: var(--umb-sign-bundle-bg); + } + `, + ]; +} + +export { UmbEntitySignIconElement as element }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/index.ts new file mode 100644 index 0000000000..52f7afd275 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/index.ts @@ -0,0 +1 @@ +export * from './entity-sign-icon.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/manifests.ts new file mode 100644 index 0000000000..474a9c1c7d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/manifests.ts @@ -0,0 +1,16 @@ +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'kind', + alias: 'Umb.Kind.EntitySign.Icon', + matchKind: 'icon', + matchType: 'entitySign', + manifest: { + type: 'entitySign', + kind: 'icon', + element: () => import('./entity-sign-icon.element.js'), + api: () => import('./entity-sign-icon.api.js'), + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/types.ts new file mode 100644 index 0000000000..9b74c8725e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/types.ts @@ -0,0 +1,18 @@ +import type { ManifestEntitySign, MetaEntitySign } from '../../types.js'; + +export interface ManifestEntitySignIconKind extends ManifestEntitySign { + type: 'entitySign'; + kind: 'icon'; +} + +export interface MetaEntitySignIconKind extends MetaEntitySign { + iconName: string; + label: string; + iconColorAlias?: string; +} + +declare global { + interface UmbExtensionManifestMap { + umbEntitySignIconKind: ManifestEntitySignIconKind; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/manifests.ts new file mode 100644 index 0000000000..3c77e5e44b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/manifests.ts @@ -0,0 +1,5 @@ +import { manifests as iconManifests } from './icon/manifests.js'; + +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [...iconManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/types.ts new file mode 100644 index 0000000000..50d1eb3721 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/types.ts @@ -0,0 +1 @@ +export type * from './icon/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/manifests.ts new file mode 100644 index 0000000000..0db5693f30 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/manifests.ts @@ -0,0 +1,5 @@ +import { manifests as kindsManifests } from './kinds/manifests.js'; + +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [...kindsManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/types.ts new file mode 100644 index 0000000000..4786572684 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/types.ts @@ -0,0 +1 @@ +export type * from './extensions/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entry-point.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entry-point.ts index 89763284e8..09b197e06e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entry-point.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entry-point.ts @@ -12,6 +12,7 @@ import './property-action/components/index.js'; import './menu/components/index.js'; import './extension-registry/components/index.js'; import './entity-item/global-components.js'; +import './entity-sign/components/index.js'; export const onInit: UmbEntryPointOnInit = (host, extensionRegistry) => { new UmbExtensionsApiInitializer(host, extensionRegistry, 'globalContext', [host]); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-picker-modal/icon-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-picker-modal/icon-picker-modal.element.ts index d2991a7aae..e6aa5a8bfd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-picker-modal/icon-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-picker-modal/icon-picker-modal.element.ts @@ -1,7 +1,16 @@ import type { UmbIconDefinition } from '../types.js'; import { UMB_ICON_REGISTRY_CONTEXT } from '../icon-registry.context-token.js'; import type { UmbIconPickerModalData, UmbIconPickerModalValue } from './icon-picker-modal.token.js'; -import { css, customElement, html, nothing, query, repeat, state } from '@umbraco-cms/backoffice/external/lit'; +import { + css, + customElement, + html, + ifDefined, + nothing, + query, + repeat, + state, +} from '@umbraco-cms/backoffice/external/lit'; import { extractUmbColorVariable, umbracoColors } from '@umbraco-cms/backoffice/resources'; import { umbFocus } from '@umbraco-cms/backoffice/lit-element'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; @@ -21,12 +30,6 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement !color.legacy); - @state() - private _currentIcon?: string; - - @state() - private _currentColor = 'text'; - constructor() { super(); this.consumeContext(UMB_ICON_REGISTRY_CONTEXT, (context) => { @@ -47,44 +50,32 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement { - this._currentIcon = newValue?.icon; - this._currentColor = newValue?.color ?? 'text'; - }, - '_observeModalContextValue', - ); - } - } - #changeIcon(e: InputEvent | KeyboardEvent, iconName: string) { - if (e.type == 'click' || (e.type == 'keyup' && (e as KeyboardEvent).key == 'Enter')) { - this.modalContext?.updateValue({ icon: iconName }); - } + const isActivate = e.type === 'click' || (e.type === 'keyup' && (e as KeyboardEvent).key === 'Enter'); + if (!isActivate) return; + + const nextIcon = this.value.icon === iconName ? '' : iconName; + this.modalContext?.updateValue({ icon: nextIcon }); } #onColorChange(e: UUIColorSwatchesEvent) { const colorAlias = e.target.value; this.modalContext?.updateValue({ color: colorAlias }); - this._currentColor = colorAlias; } + #clearIcon = () => { + this.modalContext?.updateValue({ icon: '' }); + }; + override render() { - // TODO: Missing localization in general. [NL] return html` - +
${this.renderSearch()}
${ // TODO: Missing localization for the color aliases. [NL] @@ -101,7 +92,18 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement
- ${this.renderIcons()} + + { + if (e.key === 'Enter' || e.key === ' ') this.#clearIcon(); + }}> + ${this.renderIcons()}
this.#changeIcon(e, icon.name)} @keyup=${(e: KeyboardEvent) => this.#changeIcon(e, icon.name)}> `, @@ -199,7 +201,7 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement = ...entityActionManifests, ...entityBulkActionManifests, ...entityManifests, + ...entitySignManifests, ...extensionManifests, ...iconRegistryManifests, ...localizationManifests, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/resources/extractUmbColorVariable.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/resources/extractUmbColorVariable.function.ts index 56120d2c8e..2db31e0259 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/resources/extractUmbColorVariable.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/resources/extractUmbColorVariable.function.ts @@ -1,3 +1,5 @@ +// TODO: This does not belong here, lets move it to the icon package. + export const umbracoColors = [ { alias: 'text', varName: '--uui-color-text' }, { alias: 'yellow', varName: '--uui-palette-sunglow' }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts index dcccd76ed5..eae3d64977 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts @@ -7,10 +7,13 @@ import { state, repeat, property, + css, type TemplateResult, } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UUIMenuItemEvent } from '@umbraco-cms/backoffice/external/uui'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import type { UmbEntityFlag } from '@umbraco-cms/backoffice/entity-flag'; export abstract class UmbTreeItemElementBase< TreeItemModelType extends UmbTreeItemModel, @@ -19,6 +22,7 @@ export abstract class UmbTreeItemElementBase< @property({ type: Object, attribute: false }) set item(newVal: TreeItemModelType) { this._item = newVal; + this._extractFlags(newVal); if (this._item) { this._label = this.localize.string(this._item?.name ?? ''); @@ -30,6 +34,18 @@ export abstract class UmbTreeItemElementBase< } protected _item?: TreeItemModelType; + /** + * @param item - The item from which to extract flags. + * @description This method is called whenever the `item` property is set. It extracts the flags from the item and assigns them to the `_flags` state property. + * This method is in some cases overridden in subclasses to customize how flags are extracted! + */ + protected _extractFlags(item: TreeItemModelType | undefined) { + this._flags = item?.flags ?? []; + } + + @state() + protected _flags?: Array; + @state() private _label?: string; @@ -212,32 +228,41 @@ export abstract class UmbTreeItemElementBase< renderIconContainer() { return html` - { - this._iconSlotHasChildren = this.#hasNodes(e); - }}> - ${!this._iconSlotHasChildren ? this.#renderIcon() : nothing} +
+ { + this._iconSlotHasChildren = this.#hasNodes(e); + }}> + ${this.#renderSigns()} +
`; } // eslint-disable-next-line @typescript-eslint/naming-convention _renderExpandSymbol?: () => HTMLElement | TemplateResult<1> | undefined; + #renderSigns() { + return this._item + ? html`${!this._iconSlotHasChildren ? this.#renderIcon() : nothing}` + : nothing; + } + #renderIcon() { - const icon = this._item?.icon; + const iconName = this._getIconName(); const isFolder = this._item?.isFolder; - if (icon) { - return html``; + if (iconName) { + return html``; } if (isFolder) { - return html``; + return html``; } - return html``; + return html``; } protected _getIconToRender(icon: string) { @@ -245,6 +270,10 @@ export abstract class UmbTreeItemElementBase< return this._isActive || this._isSelected ? iconWithoutColor : icon; } + protected _getIconName(): string | null | undefined { + return this._item?.icon; + } + renderLabel() { return html``; } @@ -293,4 +322,38 @@ export abstract class UmbTreeItemElementBase< .loading=${this._isLoadingNextChildren}> `; } + + static override styles = [ + UmbTextStyles, + css` + #icon-container { + position: relative; + font-size: 15px; + } + + uui-menu-item { + --umb-sign-bundle-bg: var(--uui-color-surface); + } + + uui-menu-item:hover { + --umb-sign-bundle-bg: var(--uui-color-surface-emphasis); + } + + uui-menu-item[active], + uui-menu-item[selected] { + --umb-sign-bundle-bg: var(--uui-color-current); + } + + uui-menu-item[selected]:hover, + uui-menu-item[active]:hover { + --umb-sign-bundle-bg: var(--uui-color-current-emphasis); + } + + #label { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + `, + ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/types.ts index 16a9c910a9..3a4fc49120 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/types.ts @@ -1,11 +1,12 @@ import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import type { UmbEntityWithOptionalFlags } from '@umbraco-cms/backoffice/entity-flag'; export type * from './entity-actions/types.js'; export type * from './extensions/types.js'; export type * from './folder/types.js'; export type * from './tree-menu-item/types.js'; -export interface UmbTreeItemModelBase extends UmbEntityModel { +export interface UmbTreeItemModelBase extends UmbEntityWithOptionalFlags { name: string; hasChildren: boolean; isFolder: boolean; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/types.ts index e119531113..c37eb0e501 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/variant/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/types.ts @@ -3,6 +3,7 @@ import type { UmbVariantId } from './variant-id.class.js'; import type { UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; import type { ScheduleRequestModel } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbPropertyValueData } from '@umbraco-cms/backoffice/property'; +import type { UmbEntityFlag } from '@umbraco-cms/backoffice/entity-flag'; export type UmbObjectWithVariantProperties = { culture: string | null; @@ -22,7 +23,9 @@ export interface UmbEntityVariantModel { segment: string | null; createDate: string | null; updateDate: string | null; + // TODO: Can we remove partial from this one: [NL] state?: string | null; + flags: Array; } /** @deprecated use `UmbEntityVariantModel` instead */ diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/vite.config.ts b/src/Umbraco.Web.UI.Client/src/packages/core/vite.config.ts index 0cc97b1c69..b77da682d3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/vite.config.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/vite.config.ts @@ -26,7 +26,9 @@ export default defineConfig({ 'entity-action/index': './entity-action/index.ts', 'entity-bulk-action/index': './entity-bulk-action/index.ts', 'entity-create-option-action/index': './entity-create-option-action/index.ts', + 'entity-flag/index': './entity-flag/index.ts', 'entity-item/index': './entity-item/index.ts', + 'entity-sign/index': './entity-sign/index.ts', 'entity/index': './entity/index.ts', 'entry-point': 'entry-point.ts', 'event/index': './event/index.ts', diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/repository/detail/document-blueprint-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/repository/detail/document-blueprint-detail.server.data-source.ts index ca1fc64d37..6e853034a9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/repository/detail/document-blueprint-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/repository/detail/document-blueprint-detail.server.data-source.ts @@ -44,6 +44,7 @@ export class UmbDocumentBlueprintServerDataSource implements UmbDetailDataSource }, values: [], variants: [], + flags: [], ...preset, }; @@ -211,12 +212,14 @@ export class UmbDocumentBlueprintServerDataSource implements UmbDetailDataSource updateDate: variant.updateDate, scheduledPublishDate: variant.scheduledPublishDate || null, scheduledUnpublishDate: variant.scheduledUnpublishDate || null, + flags: variant.flags, }; }), documentType: { unique: data.documentType.id, collection: data.documentType.collection ? { unique: data.documentType.collection.id } : null, }, + flags: data.flags, }; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/types.ts index c6a4d4f664..eb930d5c2b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/types.ts @@ -2,12 +2,12 @@ import type { UmbDocumentBlueprintEntityType } from './entity.js'; import type { UmbEntityVariantModel, UmbEntityVariantOptionModel } from '@umbraco-cms/backoffice/variant'; import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models'; import { DocumentVariantStateModel as UmbDocumentBlueprintVariantState } from '@umbraco-cms/backoffice/external/backend-api'; -import type { UmbElementValueModel } from '@umbraco-cms/backoffice/content'; +import type { UmbContentDetailModel, UmbElementValueModel } from '@umbraco-cms/backoffice/content'; export { UmbDocumentBlueprintVariantState }; export type * from './tree/types.js'; export type * from './workspace/types.js'; -export interface UmbDocumentBlueprintDetailModel { +export interface UmbDocumentBlueprintDetailModel extends UmbContentDetailModel { documentType: { unique: string; collection: UmbReferenceByUnique | null; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/repository/document-collection.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/repository/document-collection.server.data-source.ts index 76787a34dc..816687f36b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/repository/document-collection.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/repository/document-collection.server.data-source.ts @@ -49,6 +49,7 @@ export class UmbDocumentCollectionServerDataSource implements UmbCollectionDataS isTrashed: item.isTrashed, sortOrder: item.sortOrder, updater: item.updater, + flags: item.flags, values: item.values.map((item) => { return { alias: item.alias, @@ -69,6 +70,7 @@ export class UmbDocumentCollectionServerDataSource implements UmbCollectionDataS state: item.state, createDate: new Date(item.createDate), updateDate: new Date(item.updateDate), + flags: item.flags, }; }), }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/types.ts index 6b1c9ddf49..de40f79a20 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/types.ts @@ -1,6 +1,7 @@ import type { UmbDocumentEntityType } from '../entity.js'; -import type { UmbDocumentItemVariantModel } from '../item/repository/types.js'; +import type { UmbDocumentItemVariantModel } from '../item/types.js'; import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import type { UmbEntityWithFlags } from '@umbraco-cms/backoffice/entity-flag'; import type { UmbCollectionFilterModel } from '@umbraco-cms/backoffice/collection'; export interface UmbDocumentCollectionFilterModel extends UmbCollectionFilterModel { @@ -12,7 +13,7 @@ export interface UmbDocumentCollectionFilterModel extends UmbCollectionFilterMod userDefinedProperties: Array<{ alias: string; header: string; isSystem: boolean }>; } -export interface UmbDocumentCollectionItemModel { +export interface UmbDocumentCollectionItemModel extends UmbEntityWithFlags { ancestors: Array; creator?: string | null; documentType: { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/constants.ts index 8b2ea67a99..6843254db2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/constants.ts @@ -2,6 +2,7 @@ export { UMB_DOCUMENT_ENTITY_TYPE, UMB_DOCUMENT_ROOT_ENTITY_TYPE } from './entit export * from './collection/constants.js'; export * from './entity-actions/constants.js'; +export type * from './entity-sign/constants.js'; export * from './entity-bulk-actions/constants.js'; export * from './item/constants.js'; export * from './menu/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/constants.ts new file mode 100644 index 0000000000..c5ccfc00e5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/constants.ts @@ -0,0 +1,2 @@ +export type * from './has-schedule/constants.js'; +export type * from './has-pending-changes/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/has-pending-changes/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/has-pending-changes/constants.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/has-pending-changes/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/has-pending-changes/manifests.ts new file mode 100644 index 0000000000..c76ae71c84 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/has-pending-changes/manifests.ts @@ -0,0 +1,13 @@ +import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; + +export const manifests: Array = [ + { + type: 'entitySign', + kind: 'icon', + alias: 'Umb.EntitySign.Document.HasPendingChanges', + name: 'Has Pending Changes Document Entity Sign', + forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], + forEntityFlags: ['Umb.PendingChanges'], + meta: { iconName: 'icon-edit', label: 'Unpublished changes' }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/has-schedule/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/has-schedule/constants.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/has-schedule/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/has-schedule/manifests.ts new file mode 100644 index 0000000000..4486eb1573 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/has-schedule/manifests.ts @@ -0,0 +1,19 @@ +import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; + +export const manifests: Array = [ + { + type: 'entitySign', + kind: 'icon', + alias: 'Umb.EntitySign.Document.HasScheduledPublish', + name: 'Document has scheduled publish Entity Sign', + forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], + forEntityFlags: ['Umb.ScheduledForPublish'], + overwrites: 'Umb.EntitySign.Document.HasPendingChanges', + weight: 500, + meta: { + iconName: 'icon-time', + label: 'Scheduled publishing', + iconColorAlias: 'green', + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/is-protected/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/is-protected/constants.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/is-protected/manifest.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/is-protected/manifest.ts new file mode 100644 index 0000000000..a9ef60b149 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/is-protected/manifest.ts @@ -0,0 +1,16 @@ +import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; + +export const manifests: UmbExtensionManifest = { + type: 'entitySign', + kind: 'icon', + alias: 'Umb.EntitySign.Document.IsProtected', + name: 'Is Protected Document Entity Sign', + forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE], + forEntityFlags: ['Umb.IsProtected'], + weight: 1000, + meta: { + iconName: 'icon-lock', + label: 'Protected', + iconColorAlias: 'red', + }, +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/manifests.ts new file mode 100644 index 0000000000..3e79eacb50 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/manifests.ts @@ -0,0 +1,9 @@ +import { manifests as protectedManifest } from './is-protected/manifest.js'; +import { manifests as scheduleManifests } from './has-schedule/manifests.js'; +import { manifests as pendingChangesManifests } from './has-pending-changes/manifests.js'; + +export const manifests: Array = [ + protectedManifest, + ...scheduleManifests, + ...pendingChangesManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/item/document-item-data-resolver.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/item/document-item-data-resolver.ts index e9aacc8f81..f27afee63b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/item/document-item-data-resolver.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/item/document-item-data-resolver.ts @@ -2,13 +2,28 @@ import { UmbDocumentVariantState } from '../types.js'; import type { UmbDocumentItemModel } from './types.js'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbEntityFlag } from '@umbraco-cms/backoffice/entity-flag'; import type { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; -import { UmbBasicState, UmbBooleanState, UmbObjectState, UmbStringState } from '@umbraco-cms/backoffice/observable-api'; +import { + UmbArrayState, + UmbBasicState, + UmbBooleanState, + UmbObjectState, + UmbStringState, + type Observable, +} from '@umbraco-cms/backoffice/observable-api'; import { type UmbVariantContext, UMB_VARIANT_CONTEXT } from '@umbraco-cms/backoffice/variant'; import type { UmbItemDataResolver } from '@umbraco-cms/backoffice/entity-item'; type UmbDocumentItemDataResolverModel = Omit; +function isVariantsInvariant(variants: Array<{ culture: string | null }>): boolean { + return variants?.[0]?.culture === null; +} +function findVariant(variants: Array, culture: string): T | undefined { + return variants.find((x) => x.culture === culture); +} + /** * A controller for resolving data for a document item * @exports @@ -31,8 +46,8 @@ export class UmbDocumentItemDataResolver(undefined); + public readonly state = this.#state.asObservable() as Observable; #isDraft = new UmbBooleanState(undefined); public readonly isDraft = this.#isDraft.asObservable(); @@ -43,6 +58,9 @@ export class UmbDocumentItemDataResolver(undefined); public readonly updateDate = this.#updateDate.asObservable(); + #flags = new UmbArrayState([], (data) => data.alias); + public readonly flags = this.#flags.asObservable(); + #variantContext?: UmbVariantContext; #fallbackCulture?: string | null; #displayCulture?: string | null; @@ -148,8 +166,7 @@ export class UmbDocumentItemDataResolver { - const variant = await this.#getCurrentVariant(); - return variant?.state; + return await this.observe(this.state).asPromise(); } /** @@ -207,27 +224,33 @@ export class UmbDocumentItemDataResolver x.culture === culture); - } - - async #getCurrentVariant() { - if (this.#isInvariant()) { - return this.getData()?.variants?.[0]; + #setFlags() { + const data = this.getData(); + if (!data) { + this.#flags.setValue([]); + return; } - return this.#findVariant(this.#displayCulture!); + const flags = data.flags ?? []; + const variantFlags = this.#getCurrentVariant()?.flags ?? []; + this.#flags.setValue([...flags, ...variantFlags]); } - #isInvariant() { - return this.getData()?.variants?.[0]?.culture === null; + #getCurrentVariant() { + const variants = this.getData()?.variants; + if (!variants) return undefined; + + if (isVariantsInvariant(variants)) { + return variants[0]; + } + + return findVariant(variants, this.#displayCulture!); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/item/repository/document-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/item/repository/document-item.server.data-source.ts index 70b96553a2..996afcc869 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/item/repository/document-item.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/item/repository/document-item.server.data-source.ts @@ -54,10 +54,12 @@ const mapper = (item: DocumentItemResponseModel): UmbDocumentItemModel => { culture: variant.culture || null, name: variant.name, state: variant.state, + flags: variant.flags, // TODO: [v17] Implement dates when available in the API. [LK] //createDate: new Date(variant.createDate), //updateDate: new Date(variant.updateDate), }; }), + flags: item.flags, }; }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/item/repository/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/item/repository/types.ts index 2ed94e972e..8826cb6f37 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/item/repository/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/item/repository/types.ts @@ -1,9 +1,10 @@ import type { UmbDocumentEntityType } from '../../entity.js'; import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; +import type { UmbEntityFlag, UmbEntityWithFlags } from '@umbraco-cms/backoffice/entity-flag'; import type { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models'; -export interface UmbDocumentItemModel { +export interface UmbDocumentItemModel extends UmbEntityWithFlags { documentType: { unique: string; icon: string; @@ -24,4 +25,5 @@ export interface UmbDocumentItemVariantModel { state: DocumentVariantStateModel | null; createDate?: Date; updateDate?: Date; + flags: Array; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts index ab1b483f43..11ea0cf492 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts @@ -2,6 +2,7 @@ import { manifests as auditLogManifests } from './audit-log/manifests.js'; import { manifests as collectionManifests } from './collection/manifests.js'; import { manifests as entityActionManifests } from './entity-actions/manifests.js'; import { manifests as entityBulkActionManifests } from './entity-bulk-actions/manifests.js'; +import { manifests as entitySignManifests } from './entity-sign/manifests.js'; import { manifests as globalContextManifests } from './global-contexts/manifests.js'; import { manifests as itemManifests } from './item/manifests.js'; import { manifests as menuManifests } from './menu/manifests.js'; @@ -27,6 +28,7 @@ export const manifests: Array = ...collectionManifests, ...entityActionManifests, ...entityBulkActionManifests, + ...entitySignManifests, ...globalContextManifests, ...itemManifests, ...menuManifests, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/save-modal/document-save-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/save-modal/document-save-modal.stories.ts index f5e0d3faae..37e0b2ff97 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/save-modal/document-save-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/save-modal/document-save-modal.stories.ts @@ -22,6 +22,7 @@ const modalData: UmbDocumentSaveModalData = { segment: null, scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, language: { entityType: 'language', @@ -46,6 +47,7 @@ const modalData: UmbDocumentSaveModalData = { updateDate: null, scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, language: { entityType: 'language', @@ -70,6 +72,7 @@ const modalData: UmbDocumentSaveModalData = { segment: null, scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, language: { entityType: 'language', @@ -121,6 +124,7 @@ this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (modalManager) => { publishDate: null, updateDate: null, segment: null, + flags: [], }, language: { entityType: 'language', @@ -143,6 +147,7 @@ this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (modalManager) => { publishDate: null, updateDate: null, segment: null, + flags: [], }, language: { entityType: 'language', diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/pending-changes/document-published-pending-changes.manager.test.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/pending-changes/document-published-pending-changes.manager.test.ts index 43e091aa4c..d885e1fa31 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/pending-changes/document-published-pending-changes.manager.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/pending-changes/document-published-pending-changes.manager.test.ts @@ -55,6 +55,7 @@ describe('UmbSelectionManager', () => { collection: null, }, isTrashed: false, + flags: [], variants: [ { state: DocumentVariantStateModel.PUBLISHED, @@ -66,6 +67,7 @@ describe('UmbSelectionManager', () => { updateDate: '2023-02-06T15:32:24.957009', scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, ], values: [ @@ -120,6 +122,7 @@ describe('UmbSelectionManager', () => { collection: null, }, isTrashed: false, + flags: [], variants: [ { state: DocumentVariantStateModel.PUBLISHED, @@ -131,6 +134,7 @@ describe('UmbSelectionManager', () => { updateDate: '2023-02-06T15:32:24.957009', scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, { state: DocumentVariantStateModel.PUBLISHED, @@ -142,6 +146,7 @@ describe('UmbSelectionManager', () => { updateDate: '2023-02-06T15:32:24.957009', scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, ], values: [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.stories.ts index 54a9d07742..fee181962e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish-with-descendants/modal/document-publish-with-descendants-modal.stories.ts @@ -25,6 +25,7 @@ const modalData: UmbDocumentPublishWithDescendantsModalData = { segment: null, scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, language: { entityType: 'language', @@ -49,6 +50,7 @@ const modalData: UmbDocumentPublishWithDescendantsModalData = { updateDate: null, scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, language: { entityType: 'language', @@ -73,6 +75,7 @@ const modalData: UmbDocumentPublishWithDescendantsModalData = { segment: null, scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, language: { entityType: 'language', diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-bulk-action/publish.bulk-action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-bulk-action/publish.bulk-action.ts index e52342a7b4..f190bfc2c7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-bulk-action/publish.bulk-action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/entity-bulk-action/publish.bulk-action.ts @@ -54,6 +54,7 @@ export class UmbDocumentPublishEntityBulkAction extends UmbEntityBulkActionBase< segment: null, scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, unique: new UmbVariantId(language.unique, null).toString(), culture: language.unique, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.stories.ts index 8d32c68d3e..06a6f7c5de 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/modal/document-publish-modal.stories.ts @@ -22,6 +22,7 @@ const modalData: UmbDocumentPublishModalData = { segment: null, scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, language: { entityType: 'language', @@ -46,6 +47,7 @@ const modalData: UmbDocumentPublishModalData = { updateDate: null, scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, language: { entityType: 'language', @@ -70,6 +72,7 @@ const modalData: UmbDocumentPublishModalData = { segment: null, scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, language: { entityType: 'language', @@ -121,6 +124,7 @@ this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (modalManager) => { publishDate: null, updateDate: null, segment: null, + flags: [], }, language: { entityType: 'language', @@ -143,6 +147,7 @@ this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (modalManager) => { publishDate: null, updateDate: null, segment: null, + flags: [], }, language: { entityType: 'language', diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.server.data-source.ts index 8689a74f64..5da4d1e6e3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/repository/document-publishing.server.data-source.ts @@ -180,6 +180,7 @@ export class UmbDocumentPublishingServerDataSource { updateDate: variant.updateDate, scheduledPublishDate: variant.scheduledPublishDate || null, scheduledUnpublishDate: variant.scheduledUnpublishDate || null, + flags: variant.flags, }; }), template: data.template ? { unique: data.template.id } : null, @@ -189,6 +190,7 @@ export class UmbDocumentPublishingServerDataSource { icon: data.documentType.icon, }, isTrashed: data.isTrashed, + flags: data.flags, }; return { data: document }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.stories.ts index 84453c2efc..901efa458d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/schedule-publish/modal/document-schedule-modal.stories.ts @@ -24,6 +24,7 @@ const modalData: UmbDocumentScheduleModalData = { segment: null, scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, language: { entityType: 'language', @@ -48,6 +49,7 @@ const modalData: UmbDocumentScheduleModalData = { segment: null, scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, language: { entityType: 'language', @@ -99,6 +101,7 @@ this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (modalManager) => { publishDate: null, updateDate: null, segment: null, + flags: [], }, language: { entityType: 'language', diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-bulk-action/unpublish.bulk-action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-bulk-action/unpublish.bulk-action.ts index 243e631ea1..a01416791b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-bulk-action/unpublish.bulk-action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/entity-bulk-action/unpublish.bulk-action.ts @@ -53,6 +53,7 @@ export class UmbDocumentUnpublishEntityBulkAction extends UmbEntityBulkActionBas segment: null, scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, unique: new UmbVariantId(language.unique, null).toString(), culture: language.unique, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.stories.ts index 40ef10377f..5a8481f4fe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/unpublish/modal/document-unpublish-modal.stories.ts @@ -25,6 +25,7 @@ const modalData: UmbDocumentUnpublishModalData = { segment: null, scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, language: { entityType: 'language', @@ -49,6 +50,7 @@ const modalData: UmbDocumentUnpublishModalData = { updateDate: null, scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, language: { entityType: 'language', @@ -73,6 +75,7 @@ const modalData: UmbDocumentUnpublishModalData = { segment: null, scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], }, language: { entityType: 'language', @@ -124,6 +127,7 @@ this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (modalManager) => { publishDate: null, updateDate: null, segment: null, + flags: [] }, language: { entityType: 'language', @@ -146,6 +150,7 @@ this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (modalManager) => { publishDate: null, updateDate: null, segment: null, + flags: [] }, language: { entityType: 'language', diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/data/document-recycle-bin-tree.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/data/document-recycle-bin-tree.server.data-source.ts index 91567e8909..aebf809208 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/data/document-recycle-bin-tree.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/tree/data/document-recycle-bin-tree.server.data-source.ts @@ -79,10 +79,13 @@ const mapper = (item: DocumentRecycleBinItemResponseModel): UmbDocumentRecycleBi culture: variant.culture || null, segment: null, // TODO: add segment to the backend API? state: variant.state, + flags: variant.flags ?? [], }; }), name: item.variants[0]?.name, // TODO: this is not correct. We need to get it from the variants. This is a temp solution. isFolder: false, createDate: item.createDate, + // TODO: Recycle bin items should have flags, but the API does not return any at the moment. [NL] + flags: (item as any).flags ?? [], }; }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts index b051f6ed3f..315ebbbf24 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts @@ -55,6 +55,7 @@ export class UmbDocumentServerDataSource isTrashed: false, values: [], variants: [], + flags: [], }; const scaffold = umbDeepMerge(preset, defaultData); @@ -102,6 +103,7 @@ export class UmbDocumentServerDataSource updateDate: variant.updateDate, scheduledPublishDate: variant.scheduledPublishDate || null, scheduledUnpublishDate: variant.scheduledUnpublishDate || null, + flags: variant.flags, }; }), template: data.template ? { unique: data.template.id } : null, @@ -111,6 +113,7 @@ export class UmbDocumentServerDataSource icon: data.documentType.icon, }, isTrashed: data.isTrashed, + flags: data.flags, }; return { data: document }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/document-search.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/document-search.server.data-source.ts index d6a4cdae75..d91ec198f5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/document-search.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/search/document-search.server.data-source.ts @@ -66,8 +66,10 @@ export class UmbDocumentSearchServerDataSource culture: variant.culture || null, name: variant.name, state: variant.state, + flags: variant.flags, }; }), + flags: item.flags, }; }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/tree-item/document-tree-item.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/tree-item/document-tree-item.context.ts index fc9b250eb7..29cbc3d63b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/tree-item/document-tree-item.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/tree-item/document-tree-item.context.ts @@ -27,6 +27,7 @@ export class UmbDocumentTreeItemContext extends UmbDefaultTreeItemContext< return hasCollection || hasChildren; }, ); + readonly flags = this.#item.flags; // TODO: Move to API readonly ancestors = this._treeItem.asObservablePart((item) => item?.ancestors ?? []); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/tree-item/document-tree-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/tree-item/document-tree-item.element.ts index 7ef228559f..85fcec66c3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/tree-item/document-tree-item.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/tree-item/document-tree-item.element.ts @@ -1,7 +1,6 @@ import type { UmbDocumentTreeItemModel } from '../types.js'; import type { UmbDocumentTreeItemContext } from './document-tree-item.context.js'; -import { css, html, nothing, customElement, classMap, state, property } from '@umbraco-cms/backoffice/external/lit'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { css, html, customElement, state, property, classMap } from '@umbraco-cms/backoffice/external/lit'; import { UmbTreeItemElementBase } from '@umbraco-cms/backoffice/tree'; @customElement('umb-document-tree-item') @@ -10,6 +9,7 @@ export class UmbDocumentTreeItemElement extends UmbTreeItemElementBase< UmbDocumentTreeItemContext > { #api: UmbDocumentTreeItemContext | undefined; + @property({ type: Object, attribute: false }) public override get api(): UmbDocumentTreeItemContext | undefined { return this.#api; @@ -20,12 +20,13 @@ export class UmbDocumentTreeItemElement extends UmbTreeItemElementBase< if (this.#api) { this.observe(this.#api.name, (name) => (this._name = name || '')); this.observe(this.#api.isDraft, (isDraft) => (this._isDraft = isDraft || false)); - this.observe(this.#api.icon, (icon) => (this._icon = icon || '')); this.observe(this.#api.hasCollection, (has) => { const oldValue = this._forceShowExpand; this._forceShowExpand = has; this.requestUpdate('_forceShowExpand', oldValue); }); + this.observe(this.#api.icon, (icon) => (this.#icon = icon || '')); + this.observe(this.#api.flags, (flags) => (this._flags = flags || '')); } super.api = value; @@ -34,20 +35,22 @@ export class UmbDocumentTreeItemElement extends UmbTreeItemElementBase< @state() private _name = ''; - @state() - private _isDraft = false; + /** + * @internal + * Indicates whether the document is a draft, this is controlled internally but present as an attribute as it affects styling. + */ + @property({ type: Boolean, reflect: true, attribute: 'draft' }) + protected _isDraft = false; - @state() - private _icon = ''; + #icon: string | null | undefined; - override renderIconContainer() { - const icon = this._icon; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected override _extractFlags(item: UmbDocumentTreeItemModel | undefined) { + // Empty on purpose and NOT calling super to prevent doing what the base does. [NL] + } - return html` - - ${icon ? html` ` : nothing} - - `; + protected override _getIconName(): string | null | undefined { + return this.#icon; } // eslint-disable-next-line @typescript-eslint/naming-convention @@ -66,62 +69,12 @@ export class UmbDocumentTreeItemElement extends UmbTreeItemElementBase< } static override styles = [ - UmbTextStyles, + ...UmbTreeItemElementBase.styles, css` - #icon-container { - position: relative; + :host([draft]) #label { + opacity: 0.6; } - - #icon { - vertical-align: middle; - } - - #label { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - #state-icon { - position: absolute; - bottom: -5px; - right: -5px; - font-size: 10px; - background: var(--uui-color-surface); - width: 14px; - height: 14px; - border-radius: 100%; - line-height: 14px; - } - - :hover #state-icon { - background: var(--uui-color-surface-emphasis); - } - - /** Active */ - [active] #state-icon { - background: var(--uui-color-current); - } - - [active]:hover #state-icon { - background: var(--uui-color-current-emphasis); - } - - /** Selected */ - [selected] #state-icon { - background-color: var(--uui-color-selected); - } - - [selected]:hover #state-icon { - background-color: var(--uui-color-selected-emphasis); - } - - /** Disabled */ - [disabled] #state-icon { - background-color: var(--uui-color-disabled); - } - - .draft { + :host([draft]) umb-icon { opacity: 0.6; } `, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/types.ts index 7afd0ae918..c91ef5eede 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/types.ts @@ -8,8 +8,9 @@ import type { import type { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models'; import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import type { UmbEntityFlag, UmbEntityWithFlags } from '@umbraco-cms/backoffice/entity-flag'; -export interface UmbDocumentTreeItemModel extends UmbTreeItemModel { +export interface UmbDocumentTreeItemModel extends Omit, UmbEntityWithFlags { ancestors: Array; entityType: UmbDocumentEntityType; noAccess: boolean; @@ -33,6 +34,7 @@ export interface UmbDocumentTreeItemVariantModel { culture: string | null; segment: string | null; state: DocumentVariantStateModel | null; // TODO: make our own enum for this. We might have states for "unsaved changes" etc. + flags: Array; } export interface UmbDocumentTreeRootItemsRequestArgs extends UmbTreeRootItemsRequestArgs { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/constants.ts index b434f6d318..0a86f3501b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/constants.ts @@ -15,4 +15,5 @@ export const UMB_DOCUMENT_DETAIL_MODEL_VARIANT_SCAFFOLD: UmbDocumentVariantModel updateDate: null, scheduledPublishDate: null, scheduledUnpublishDate: null, + flags: [], } as const; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts index dfffd07267..66849f58ff 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts @@ -27,6 +27,7 @@ export class UmbMediaCollectionContext extends UmbDefaultCollectionContext< updateDate: date, createDate: date, entityType: UMB_MEDIA_PLACEHOLDER_ENTITY_TYPE, + flags: [], ...placeholder, })) .reverse(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/repository/media-collection.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/repository/media-collection.server.data-source.ts index b1a305495b..e6d6ef4862 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/repository/media-collection.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/repository/media-collection.server.data-source.ts @@ -45,6 +45,7 @@ export class UmbMediaCollectionServerDataSource implements UmbCollectionDataSour values: item.values.map((item) => { return { alias: item.alias, value: item.value as string }; }), + flags: item.flags, }; return model; }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts index 06712e08d8..9cb9332e60 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts @@ -1,5 +1,6 @@ import type { UmbFileDropzoneItemStatus } from '@umbraco-cms/backoffice/dropzone'; import type { UmbCollectionFilterModel } from '@umbraco-cms/backoffice/collection'; +import type { UmbEntityWithFlags } from '@umbraco-cms/backoffice/entity-flag'; export interface UmbMediaCollectionFilterModel extends UmbCollectionFilterModel { unique?: string; @@ -9,7 +10,7 @@ export interface UmbMediaCollectionFilterModel extends UmbCollectionFilterModel userDefinedProperties: Array<{ alias: string; header: string; isSystem: boolean }>; } -export interface UmbMediaCollectionItemModel { +export interface UmbMediaCollectionItemModel extends UmbEntityWithFlags { unique: string; entityType: string; contentTypeAlias?: string; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/media-dropzone.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/media-dropzone.manager.ts index 2fd41b6421..bcb5c86274 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/media-dropzone.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/media-dropzone.manager.ts @@ -190,7 +190,7 @@ export class UmbMediaDropzoneManager extends UmbDropzoneManager { const preset: Partial = { unique: item.unique, mediaType: { unique: mediaTypeUnique, collection: null }, - variants: [{ culture: null, segment: null, createDate: null, updateDate: null, name }], + variants: [{ culture: null, segment: null, createDate: null, updateDate: null, flags: [], name }], values: item.temporaryFile ? [umbracoFile] : undefined, }; const { data } = await this.#mediaDetailRepository.createScaffold(preset); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/components/media-picker-folder-path.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/components/media-picker-folder-path.element.ts index 3c2f0422c0..7aa35f9370 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/components/media-picker-folder-path.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/components/media-picker-folder-path.element.ts @@ -117,6 +117,7 @@ export class UmbMediaPickerFolderPathElement extends UmbLitElement { name: newName, createDate: null, updateDate: null, + flags: [], }, ], }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/detail/media-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/detail/media-detail.server.data-source.ts index 614e79d94e..dede293238 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/detail/media-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/detail/media-detail.server.data-source.ts @@ -45,6 +45,7 @@ export class UmbMediaServerDataSource extends UmbControllerBase implements UmbDe icon: mediaTypeIcon, }, isTrashed: false, + flags: [], values: [], variants: [ { @@ -53,6 +54,7 @@ export class UmbMediaServerDataSource extends UmbControllerBase implements UmbDe name: '', createDate: null, updateDate: null, + flags: [], }, ], }; @@ -90,6 +92,8 @@ export class UmbMediaServerDataSource extends UmbControllerBase implements UmbDe name: variant.name, createDate: variant.createDate, updateDate: variant.updateDate, + // TODO: Media variant flags are not yet implemented in the backend. + flags: [], }; }), mediaType: { @@ -98,6 +102,7 @@ export class UmbMediaServerDataSource extends UmbControllerBase implements UmbDe icon: data.mediaType.icon, }, isTrashed: data.isTrashed, + flags: data.flags, }; return { data: media }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/tree/types.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/tree/types.ts index 8f094c91b3..be7a5233cf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/tree/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/tree/types.ts @@ -27,6 +27,8 @@ export interface UmbMediaTreeRootModel extends UmbTreeRootModel { export interface UmbMediaTreeItemVariantModel { name: string; culture: string | null; + // Notice: Media variant flags are not yet implemented in the backend. + //flags: Array; } export interface UmbMediaTreeRootItemsRequestArgs extends UmbTreeRootItemsRequestArgs { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/constants.ts index 8a3af27b20..d823f4f0b8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/constants.ts @@ -10,4 +10,5 @@ export const UMB_MEMBER_DETAIL_MODEL_VARIANT_SCAFFOLD: UmbMediaVariantModel = { name: '', createDate: null, updateDate: null, + flags: [], } as const; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/collection/repository/member-collection.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/collection/repository/member-collection.server.data-source.ts index 0c9742104d..f35676c7c9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/collection/repository/member-collection.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/collection/repository/member-collection.server.data-source.ts @@ -62,6 +62,7 @@ export class UmbMemberCollectionServerDataSource implements UmbCollectionDataSou memberType: { unique: item.memberType.id, icon: item.memberType.icon }, username: item.username, values: item.values as UmbMemberValueModel[], + flags: item.flags, }; return memberDetail; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/detail/member-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/detail/member-detail.server.data-source.ts index 134cb2bfe1..b96137e638 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/detail/member-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/repository/detail/member-detail.server.data-source.ts @@ -53,6 +53,7 @@ export class UmbMemberServerDataSource extends UmbControllerBase implements UmbD lastPasswordChangeDate: null, groups: [], values: [], + flags: [], variants: [ { name: '', @@ -60,6 +61,7 @@ export class UmbMemberServerDataSource extends UmbControllerBase implements UmbD segment: null, createDate: new Date().toISOString(), updateDate: new Date().toISOString(), + flags: [], }, ], }; @@ -120,8 +122,11 @@ export class UmbMemberServerDataSource extends UmbControllerBase implements UmbD name: variant.name, createDate: variant.createDate, updateDate: variant.updateDate, + // TODO: Transfer member flags when available in the API: [NL] + flags: [], //variant.flags, }; }), + flags: data.flags, }; return { data: Member }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/constants.ts index 465892c416..0664db72ac 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/constants.ts @@ -6,6 +6,7 @@ export const UMB_MEMBER_DETAIL_MODEL_VARIANT_SCAFFOLD: UmbMemberVariantModel = { name: '', createDate: null, updateDate: null, + flags: [], } as const; export { UMB_MEMBER_WORKSPACE_CONTEXT } from './member-workspace.context-token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/manifests.ts index 2479e6b7a3..8cf5a3bf17 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/manifests.ts @@ -45,6 +45,7 @@ const propertyEditorUiManifest: ManifestPropertyEditorUi = { label: 'Workspace View icon', description: "The icon for the Collection's Workspace View.", propertyEditorUiAlias: 'Umb.PropertyEditorUi.IconPicker', + config: [{ alias: 'placeholder', value: 'icon-grid' }], }, { alias: 'tabName', diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/icon-picker/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/icon-picker/manifests.ts index ab1792f1c8..780ddf721e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/icon-picker/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/icon-picker/manifests.ts @@ -8,6 +8,16 @@ export const manifests: Array = [ label: 'Icon Picker', icon: 'icon-autofill', group: 'common', + settings: { + properties: [ + { + alias: 'placeholder', + label: 'Placeholder icon (empty state)', + description: 'Icon name to show when no icon is selected', + propertyEditorUiAlias: 'Umb.PropertyEditorUi.IconPicker', + }, + ], + }, }, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/icon-picker/property-editor-ui-icon-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/icon-picker/property-editor-ui-icon-picker.element.ts index 60298ab57a..fffedbbaec 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/icon-picker/property-editor-ui-icon-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/icon-picker/property-editor-ui-icon-picker.element.ts @@ -1,5 +1,8 @@ -import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor'; +import { html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import type { + UmbPropertyEditorConfigCollection, + UmbPropertyEditorUiElement, +} from '@umbraco-cms/backoffice/property-editor'; import { umbOpenModal } from '@umbraco-cms/backoffice/modal'; import { UMB_ICON_PICKER_MODAL } from '@umbraco-cms/backoffice/icon'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -35,11 +38,29 @@ export class UmbPropertyEditorUIIconPickerElement extends UmbLitElement implemen @state() private _color = ''; + @state() + private _placeholderIcon = ''; + + public set config(config: UmbPropertyEditorConfigCollection | undefined) { + if (!config) return; + const placeholder = config.getValueByAlias('placeholder'); + this._placeholderIcon = typeof placeholder === 'string' ? placeholder : ''; + } + private async _openModal() { - const data = await umbOpenModal(this, UMB_ICON_PICKER_MODAL).catch(() => undefined); + const data = await umbOpenModal(this, UMB_ICON_PICKER_MODAL, { + value: { + icon: this._icon, + color: this._color, + }, + data: { placeholder: this._placeholderIcon }, + }).catch(() => undefined); + if (!data) return; - if (data.color) { + if (!data.icon) { + this.value = ''; + } else if (data.color) { this.value = `${data.icon} color-${data.color}`; } else { this.value = data.icon as string; @@ -49,15 +70,21 @@ export class UmbPropertyEditorUIIconPickerElement extends UmbLitElement implemen } override render() { + const isEmpty = !this._icon; + return html` - ${this._color - ? html` ` - : html` `} + ${isEmpty + ? html` ` + : this._color + ? html` + + ` + : html``} `; } diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index eebc431a10..ef64a1e410 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -77,6 +77,8 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js "./src/packages/core/entity-create-option-action/index.ts" ], "@umbraco-cms/backoffice/entity-item": ["./src/packages/core/entity-item/index.ts"], + "@umbraco-cms/backoffice/entity-sign": ["./src/packages/core/entity-sign/index.ts"], + "@umbraco-cms/backoffice/entity-flag": ["./src/packages/core/entity-flag/index.ts"], "@umbraco-cms/backoffice/entity": ["./src/packages/core/entity/index.ts"], "@umbraco-cms/backoffice/event": ["./src/packages/core/event/index.ts"], "@umbraco-cms/backoffice/extension-registry": ["./src/packages/core/extension-registry/index.ts"],