From dc92cf4b19b530002bc93b1cdc502fdb3d6c4be9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=BCger?= <93977820+OskarKruger@users.noreply.github.com> Date: Wed, 8 Oct 2025 12:11:40 +0200 Subject: [PATCH 1/3] Add accessibility label for splitview divider (#20380) * added hovering and focus border to RTE * fix main to OG * fix to main again * I'm going to cry * Added label for splitviewdivider * Added localization to divider label and updated common lang files * Removes unused import --------- Co-authored-by: Oskar kruger Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> --- .../src/assets/lang/da.ts | 1 + .../src/assets/lang/de.ts | 1 + .../src/assets/lang/en.ts | 1 + .../src/assets/lang/es.ts | 1 + .../src/assets/lang/fr.ts | 1 + .../src/assets/lang/nl.ts | 1 + .../split-panel/split-panel.element.ts | 20 ++++++++++++------- 7 files changed, 19 insertions(+), 7 deletions(-) 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 7dcb6f23ac..7836b24781 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/da.ts @@ -848,6 +848,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 b3da271e7c..f5a7f06593 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts @@ -850,6 +850,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..67aba75863 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/es.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/es.ts @@ -481,6 +481,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/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 From 193d8af9af38fae16f13161c887850c8183fea92 Mon Sep 17 00:00:00 2001 From: Engiber Lozada <89547469+engijlr@users.noreply.github.com> Date: Wed, 8 Oct 2025 12:28:44 +0200 Subject: [PATCH 2/3] =?UTF-8?q?Icon=20Picker:=20Deselect=20current=20icon?= =?UTF-8?q?=20&=20add=20=E2=80=9CNo=20icon=E2=80=9D=20tile=20inside=20the?= =?UTF-8?q?=20modal.=20(#20342)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Show selected icon and color(if any) when open the modal. * Add a button inside the modal that clears the value * Deselect the value if we click the already selected icon. * Add placeholder icon and some localization for labels * Remove unused variable * Added config for the placeholder icon in case no icon is selected. --- .../src/assets/lang/en.ts | 2 + .../src/assets/lang/es.ts | 2 + .../icon-picker-modal.element.ts | 72 ++++++++++--------- .../icon-picker-modal.token.ts | 4 +- .../property-editors/collection/manifests.ts | 1 + .../property-editors/icon-picker/manifests.ts | 10 +++ .../property-editor-ui-icon-picker.element.ts | 43 ++++++++--- 7 files changed, 90 insertions(+), 44 deletions(-) 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 f5a7f06593..5f5f6d57c0 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts @@ -531,6 +531,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', @@ -621,6 +622,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', 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 67aba75863..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', 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 = [ 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``} `; } From c6e5df40c93e74b5fe0d0c236d8f663d28d5dc88 Mon Sep 17 00:00:00 2001 From: Engiber Lozada <89547469+engijlr@users.noreply.github.com> Date: Wed, 8 Oct 2025 12:40:27 +0200 Subject: [PATCH 3/3] Backoffice: Add Entity Signs (overlay icons) to tree items. (#20328) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * entity signs folder * update package.json * entity sign extension type * implement entity sign extension * POC document has collection sign * implement icon kind * rename file * note about this being wrong * move type * change import * entity sign bundle element * implement icon kind label * Display icon and show popover on hover * Fix the popover logic * Moving the sign icon to the iconContainer to handle position * fix missing document tree icon * revert removal of icon slot render * remove unused styles * document tree item - inherit styles from the base element * correctly extend styles * revert document tree item icon change * move icon container html * add method to get an icon name * Adding delay to the popover when opens * Add animation to popover when it opens * Making the parent of the entity bundle trigger popover on hover * Display 2 icons over the main icon * Updating some styles * Position one icon on top of the other and add css style variables * Changing popover-container for position-anchor * generate server types * Using css properties to display and animate the signs * Stacked icons using grid property * Use translate property to move the icons around * Added fallback styles for firefox * formatting of state properties * implement entity flags across content types * lint fixes * fix import extension mess * await both properties for this to work * transfer flags to entity sign bundle ext initializer * is-protected entity sign * Made signs infobox show downward. * Changed px to rems * Change the manifest for the actual signs we will display * add icon color, remove unused label, add weight * changes styles + animation + slotted icon inside * Overwrite pending changes when schedule is active and added green color to schedule. * adjust animation * add background for sign * avoid re-rendering when properties are being set * Bind the flags to each sign manifest. * increase signs offset * fix document tree item draft style * Removed unused exports. * Remove duplicated hover timer logic. * Added eslint disable line to keep the empty method for future implementation. * rename class * Rename interface for optional entity flags * make alias more explicit to prevent future collisions * include alias in field name to make it clear that we do not except all colors * align function names with conventions * always include flags in document items * compose tree types * set up entity-flag module and move related types * change label --------- Co-authored-by: Niels Lyngsø Co-authored-by: Mads Rasmussen --- src/Umbraco.Web.UI.Client/package.json | 2 + .../src/packages/content/content/types.ts | 4 +- ...-detail-validation-path-translator.test.ts | 1 + .../src/packages/core/entity-flag/index.ts | 1 + .../src/packages/core/entity-flag/types.ts | 15 + .../components/entity-sign-bundle.element.ts | 277 ++++++++++++++++++ .../core/entity-sign/components/index.ts | 1 + .../extensions/entity-sign-api.interface.ts | 21 ++ .../entity-sign-element.interface.ts | 6 + .../extensions/entity-sign.extension.ts | 25 ++ .../core/entity-sign/extensions/types.ts | 3 + .../src/packages/core/entity-sign/index.ts | 2 + .../kinds/icon/entity-sign-icon.api.ts | 19 ++ .../kinds/icon/entity-sign-icon.element.ts | 37 +++ .../core/entity-sign/kinds/icon/index.ts | 1 + .../core/entity-sign/kinds/icon/manifests.ts | 16 + .../core/entity-sign/kinds/icon/types.ts | 18 ++ .../core/entity-sign/kinds/manifests.ts | 5 + .../packages/core/entity-sign/kinds/types.ts | 1 + .../packages/core/entity-sign/manifests.ts | 5 + .../src/packages/core/entity-sign/types.ts | 1 + .../src/packages/core/entry-point.ts | 1 + .../src/packages/core/manifests.ts | 2 + .../extractUmbColorVariable.function.ts | 2 + .../tree-item-base/tree-item-element-base.ts | 90 +++++- .../src/packages/core/tree/types.ts | 3 +- .../src/packages/core/variant/types.ts | 3 + .../src/packages/core/vite.config.ts | 2 + ...ent-blueprint-detail.server.data-source.ts | 3 + .../documents/document-blueprints/types.ts | 4 +- .../document-collection.server.data-source.ts | 2 + .../documents/documents/collection/types.ts | 5 +- .../packages/documents/documents/constants.ts | 1 + .../documents/entity-sign/constants.ts | 2 + .../has-pending-changes/constants.ts | 0 .../has-pending-changes/manifests.ts | 13 + .../entity-sign/has-schedule/constants.ts | 0 .../entity-sign/has-schedule/manifests.ts | 19 ++ .../entity-sign/is-protected/constants.ts | 0 .../entity-sign/is-protected/manifest.ts | 16 + .../documents/entity-sign/manifests.ts | 9 + .../item/document-item-data-resolver.ts | 75 +++-- .../document-item.server.data-source.ts | 2 + .../documents/item/repository/types.ts | 4 +- .../packages/documents/documents/manifests.ts | 2 + .../save-modal/document-save-modal.stories.ts | 5 + ...-published-pending-changes.manager.test.ts | 5 + ...-publish-with-descendants-modal.stories.ts | 3 + .../entity-bulk-action/publish.bulk-action.ts | 1 + .../modal/document-publish-modal.stories.ts | 5 + .../document-publishing.server.data-source.ts | 2 + .../modal/document-schedule-modal.stories.ts | 3 + .../unpublish.bulk-action.ts | 1 + .../modal/document-unpublish-modal.stories.ts | 5 + ...ent-recycle-bin-tree.server.data-source.ts | 3 + ...ference-response.management-api.mapping.ts | 1 + .../document-detail.server.data-source.ts | 3 + .../document-search.server.data-source.ts | 2 + .../tree/document-tree.server.data-source.ts | 2 + .../tree-item/document-tree-item.context.ts | 1 + .../tree-item/document-tree-item.element.ts | 116 ++------ .../documents/documents/tree/types.ts | 4 +- .../documents/workspace/constants.ts | 1 + .../collection/media-collection.context.ts | 1 + .../media-collection.server.data-source.ts | 1 + .../packages/media/media/collection/types.ts | 3 +- .../media/dropzone/media-dropzone.manager.ts | 2 +- .../media-picker-folder-path.element.ts | 1 + .../detail/media-detail.server.data-source.ts | 5 + .../tree/media-tree.server.data-source.ts | 3 + .../src/packages/media/media/tree/types.ts | 2 + .../media/media/workspace/constants.ts | 1 + .../member-collection.server.data-source.ts | 1 + .../member-detail.server.data-source.ts | 5 + .../member/workspace/member/constants.ts | 1 + src/Umbraco.Web.UI.Client/tsconfig.json | 2 + 76 files changed, 771 insertions(+), 141 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-flag/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-flag/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/components/entity-sign-bundle.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/components/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/extensions/entity-sign-api.interface.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/extensions/entity-sign-element.interface.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/extensions/entity-sign.extension.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/extensions/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/entity-sign-icon.api.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/entity-sign-icon.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/icon/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/kinds/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/entity-sign/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/has-pending-changes/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/has-pending-changes/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/has-schedule/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/has-schedule/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/is-protected/constants.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/is-protected/manifest.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-sign/manifests.ts diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 7a65588f17..d9145b2732 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/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/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/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts index 1e046ec818..4cccf89abb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts @@ -5,6 +5,7 @@ import { manifests as debugManifests } from './debug/manifests.js'; import { manifests as entityActionManifests } from './entity-action/manifests.js'; import { manifests as entityBulkActionManifests } from './entity-bulk-action/manifests.js'; import { manifests as entityManifests } from './entity/manifests.js'; +import { manifests as entitySignManifests } from './entity-sign/manifests.js'; import { manifests as extensionManifests } from './extension-registry/manifests.js'; import { manifests as iconRegistryManifests } from './icon-registry/manifests.js'; import { manifests as localizationManifests } from './localization/manifests.js'; @@ -32,6 +33,7 @@ export const manifests: Array = ...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 359c14f664..0b0d1764a8 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 @@ -1,8 +1,10 @@ import type { UmbTreeItemContext } from '../index.js'; import type { UmbTreeItemModel } from '../../types.js'; -import { html, ifDefined, nothing, state, repeat, property } from '@umbraco-cms/backoffice/external/lit'; +import { html, ifDefined, nothing, state, repeat, property, css } 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, @@ -11,6 +13,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 ?? ''); @@ -22,6 +25,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; @@ -139,7 +154,7 @@ export abstract class UmbTreeItemElementBase< .caretLabel=${this._isOpen ? this.localize.term('visuallyHiddenTexts_collapseChildItems') + ' ' + this._label : this.localize.term('visuallyHiddenTexts_expandChildItems') + ' ' + this._label} - label=${this._label} + label=${ifDefined(this._label)} href="${ifDefined(this._isSelectableContext ? undefined : this._href)}"> ${this.renderIconContainer()} ${this.renderLabel()} ${this.#renderActions()} ${this.#renderChildItems()} @@ -154,29 +169,38 @@ export abstract class UmbTreeItemElementBase< renderIconContainer() { return html` - { - this._iconSlotHasChildren = this.#hasNodes(e); - }}> - ${!this._iconSlotHasChildren ? this.#renderIcon() : nothing} +
+ { + this._iconSlotHasChildren = this.#hasNodes(e); + }}> + ${this.#renderSigns()} +
`; } + #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) { @@ -184,6 +208,10 @@ export abstract class UmbTreeItemElementBase< return this._isActive || this._isSelected ? iconWithoutColor : icon; } + protected _getIconName(): string | null | undefined { + return this._item?.icon; + } + renderLabel() { return html``; } @@ -224,4 +252,38 @@ export abstract class UmbTreeItemElementBase< return html` `; } + + 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 7d83defd77..188cdb52a1 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 0f91dd516d..05c926717f 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 @@ -58,6 +58,7 @@ export class UmbDocumentCollectionServerDataSource implements UmbCollectionDataS name: variant.name, sortOrder: item.sortOrder, state: variant.state, + flags: variant.flags, updateDate: item.variants .map((v) => new Date(v.updateDate)) .reduce((latest, current) => (current > latest ? current : latest)), @@ -75,6 +76,7 @@ export class UmbDocumentCollectionServerDataSource implements UmbCollectionDataS name: item.name, culture: item.culture ?? null, state: item.state, + 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 a1ec0ecc44..d080edd911 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; unique: string; entityType: UmbDocumentEntityType; 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 0a072e6793..c8ab61697c 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,12 +2,26 @@ 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 { UmbBooleanState, UmbObjectState, UmbStringState } from '@umbraco-cms/backoffice/observable-api'; +import { + type Observable, + UmbArrayState, + UmbBooleanState, + UmbObjectState, + UmbStringState, +} from '@umbraco-cms/backoffice/observable-api'; import { type UmbVariantContext, UMB_VARIANT_CONTEXT } from '@umbraco-cms/backoffice/variant'; 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 @@ -24,12 +38,15 @@ export class UmbDocumentItemDataResolver(undefined); + public readonly state = this.#state.asObservable() as Observable; #isDraft = new UmbBooleanState(undefined); public readonly isDraft = this.#isDraft.asObservable(); + #flags = new UmbArrayState([], (data) => data.alias); + public readonly flags = this.#flags.asObservable(); + #variantContext?: UmbVariantContext; #fallbackCulture?: string | null; #displayCulture?: string | null; @@ -117,8 +134,7 @@ export class UmbDocumentItemDataResolver { - const variant = await this.#getCurrentVariant(); - return variant?.state; + return await this.observe(this.state).asPromise(); } /** @@ -147,44 +163,57 @@ 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 7035a4b926..b4078e9e16 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 @@ -55,7 +55,9 @@ const mapper = (item: DocumentItemResponseModel): UmbDocumentItemModel => { 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/item/repository/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/item/repository/types.ts index 142d9e2951..e35c05fa73 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; @@ -23,4 +24,5 @@ export interface UmbDocumentItemVariantModel { name: string; culture: string | null; state: DocumentVariantStateModel | null; + 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 29ec4cf581..dccfbd2ff4 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'; @@ -26,6 +27,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/reference/repository/document-reference-response.management-api.mapping.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/reference/repository/document-reference-response.management-api.mapping.ts index ecc3b4fd23..d442433da3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/reference/repository/document-reference-response.management-api.mapping.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/reference/repository/document-reference-response.management-api.mapping.ts @@ -29,6 +29,7 @@ export class UmbDocumentReferenceResponseManagementApiDataMapping culture: null, name: data.name ?? '', state: data.published ? DocumentVariantStateModel.PUBLISHED : null, + flags: [], }, ], unique: data.id, 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/document-tree.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/document-tree.server.data-source.ts index 4bb8b14bcc..9bc3eec9c9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/document-tree.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/document-tree.server.data-source.ts @@ -88,10 +88,12 @@ const mapper = (item: DocumentTreeItemResponseModel): UmbDocumentTreeItemModel = 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, + 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 cf556635f9..6abf9e46a8 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 @@ -17,6 +17,7 @@ export class UmbDocumentTreeItemContext extends UmbDefaultTreeItemContext< readonly name = this.#item.name; readonly icon = this.#item.icon; readonly isDraft = this.#item.isDraft; + readonly flags = this.#item.flags; readonly ancestors = this._treeItem.asObservablePart((item) => item?.ancestors ?? []); readonly isTrashed = this._treeItem.asObservablePart((item) => item?.isTrashed ?? false); 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 045a85cf58..103342e3d5 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 } 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,7 +20,8 @@ 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.icon, (icon) => (this.#icon = icon || '')); + this.observe(this.#api.flags, (flags) => (this._flags = flags || '')); } super.api = value; @@ -29,108 +30,35 @@ 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` - - ${this.#renderStateIcon()} - ` - : nothing} - - `; + protected override _getIconName(): string | null | undefined { + return this.#icon; } override renderLabel() { - return html`${this._name} `; - } - - #renderStateIcon() { - if (this.item?.isProtected) { - return this.#renderIsProtectedIcon(); - } - - if (this.item?.documentType.collection) { - return this.#renderIsCollectionIcon(); - } - - return nothing; - } - - #renderIsCollectionIcon() { - return html``; - } - - #renderIsProtectedIcon() { - return html``; + return html`${this._name} `; } 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 d0e5928405..b5788eed50 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 @@ -33,6 +33,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/media-tree.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/tree/media-tree.server.data-source.ts index 156ecc39fb..13976a6de9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/tree/media-tree.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/tree/media-tree.server.data-source.ts @@ -78,8 +78,11 @@ const mapper = (item: MediaTreeItemResponseModel): UmbMediaTreeItemModel => { return { name: variant.name, culture: variant.culture || null, + // Notice: Media variant flags are not yet implemented in the backend. + //flags: variant.flags, }; }), createDate: item.createDate, + flags: item.flags, }; }; 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/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index 1d040c15b2..55875ce07d 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"],