diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts index ebbec1134f..21ef577a45 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts @@ -20,6 +20,14 @@ import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/rou import { pathFolderName } from '@umbraco-cms/backoffice/utils'; import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; +interface UmbBlockGridAreaTypeInvalidRuleType { + groupKey?: string; + key?: string; + name: string; + amount: number; + minRequirement: number; + maxRequirement: number; +} export class UmbBlockGridEntriesContext extends UmbBlockEntriesContext< typeof UMB_BLOCK_GRID_MANAGER_CONTEXT, @@ -455,14 +463,7 @@ export class UmbBlockGridEntriesContext } } - #invalidBlockTypeLimits?: Array<{ - groupKey?: string; - key?: string; - name: string; - amount: number; - minRequirement: number; - maxRequirement: number; - }>; + #invalidBlockTypeLimits?: Array; getInvalidBlockTypeLimits() { return this.#invalidBlockTypeLimits ?? []; @@ -476,57 +477,58 @@ export class UmbBlockGridEntriesContext const layoutEntries = this._layoutEntries.getValue(); - this.#invalidBlockTypeLimits = []; + this.#invalidBlockTypeLimits = this.#areaType.specifiedAllowance + .map((rule) => { + const minAllowed = rule.minAllowed || 0; + const maxAllowed = rule.maxAllowed || 0; - const hasInvalidRules = this.#areaType.specifiedAllowance.some((rule) => { - const minAllowed = rule.minAllowed || 0; - const maxAllowed = rule.maxAllowed || 0; + // For block groups: + if (rule.groupKey) { + const groupElementTypeKeys = + this._manager + ?.getBlockTypes() + .filter((blockType) => blockType.groupKey === rule.groupKey && blockType.allowInAreas === true) + .map((x) => x.contentElementTypeKey) ?? []; + const groupAmount = layoutEntries.filter((entry) => { + const contentTypeKey = this._manager!.getContentTypeKeyOfContentUdi(entry.contentUdi); + return contentTypeKey ? groupElementTypeKeys.indexOf(contentTypeKey) !== -1 : false; + }).length; - // For block groups: - if (rule.groupKey) { - const groupElementTypeKeys = - this._manager - ?.getBlockTypes() - .filter((blockType) => blockType.groupKey === rule.groupKey && blockType.allowInAreas === true) - .map((x) => x.contentElementTypeKey) ?? []; - const groupAmount = layoutEntries.filter((entry) => { - const contentTypeKey = this._manager!.getContentTypeKeyOf(entry.contentUdi); - return contentTypeKey ? groupElementTypeKeys.indexOf(contentTypeKey) !== -1 : false; - }).length; - - if (groupAmount < minAllowed || (maxAllowed > 0 && groupAmount > maxAllowed)) { - this.#invalidBlockTypeLimits!.push({ - groupKey: rule.groupKey, - name: this._manager!.getBlockGroupName(rule.groupKey) ?? '?', - amount: groupAmount, - minRequirement: minAllowed, - maxRequirement: maxAllowed, - }); - return true; + if (groupAmount < minAllowed || (maxAllowed > 0 && groupAmount > maxAllowed)) { + return { + groupKey: rule.groupKey, + name: this._manager!.getBlockGroupName(rule.groupKey) ?? '?', + amount: groupAmount, + minRequirement: minAllowed, + maxRequirement: maxAllowed, + }; + } + return undefined; } - } - // For specific elementTypes: - else if (rule.elementTypeKey) { - const amount = layoutEntries.filter((entry) => { - const contentTypeKey = this._manager!.getContentOf(entry.contentUdi)?.contentTypeKey; - return contentTypeKey === rule.elementTypeKey; - }).length; - if (amount < minAllowed || (maxAllowed > 0 ? amount > maxAllowed : false)) { - this.#invalidBlockTypeLimits!.push({ - key: rule.elementTypeKey, - name: this._manager!.getContentTypeNameOf(rule.elementTypeKey) ?? '?', - amount: amount, - minRequirement: minAllowed, - maxRequirement: maxAllowed, - }); - return true; + // For specific elementTypes: + else if (rule.elementTypeKey) { + const amount = layoutEntries.filter((entry) => { + const contentTypeKey = this._manager!.getContentOf(entry.contentUdi)?.contentTypeKey; + return contentTypeKey === rule.elementTypeKey; + }).length; + if (amount < minAllowed || (maxAllowed > 0 ? amount > maxAllowed : false)) { + return { + key: rule.elementTypeKey, + name: this._manager!.getContentTypeNameOf(rule.elementTypeKey) ?? '?', + amount: amount, + minRequirement: minAllowed, + maxRequirement: maxAllowed, + }; + } + return undefined; } - } - // Lets fail cause the rule was bad. - console.error('Invalid block type limit rule.', rule); - return false; - }); + // Lets fail cause the rule was bad. + console.error('Invalid block type limit rule.', rule); + return undefined; + }) + .filter((x) => x !== undefined) as Array; + const hasInvalidRules = this.#invalidBlockTypeLimits.length > 0; return hasInvalidRules === false; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/block-grid-type-workspace.modal-token.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/block-grid-type-workspace.modal-token.ts index f45bce646e..19d76caa88 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/block-grid-type-workspace.modal-token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/block-grid-type-workspace.modal-token.ts @@ -14,7 +14,7 @@ export const UMB_BLOCK_GRID_TYPE_WORKSPACE_MODAL = new UmbModalToken< type: 'sidebar', size: 'large', }, - data: { entityType: UMB_BLOCK_GRID_TYPE, preset: { allowAtRoot: true } }, + data: { entityType: UMB_BLOCK_GRID_TYPE, preset: { allowAtRoot: true, allowInAreas: true } }, }, // Recast the type, so the entityType data prop is not required: ) as UmbModalToken, UmbWorkspaceModalValue>; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts index 4beee2607a..0172936a37 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts @@ -48,6 +48,15 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper } private _contentUdi?: string | undefined; + /** + * Sets the element to readonly mode, meaning value cannot be changed but still able to read and select its content. + * @type {boolean} + * @attr + * @default false + */ + @property({ type: Boolean, reflect: true }) + public readonly = false; + #context = new UmbBlockListEntryContext(this); @state() @@ -252,33 +261,7 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper >${this._inlineEditingMode ? this.#renderInlineBlock() : this.#renderRefBlock()} - ${this._showContentEdit && this._workspaceEditContentPath - ? html` - - ${this._contentInvalid - ? html`!` - : nothing} - ` - : nothing} - ${this._hasSettings && this._workspaceEditSettingsPath - ? html` - - ${this._settingsInvalid - ? html`!` - : nothing} - ` - : nothing} - this.#context.requestDelete()}> - - + ${this.#renderEditContentAction()} ${this.#renderEditSettingsAction()} ${this.#renderDeleteAction()} ${!this._showContentEdit && this._contentInvalid ? html`!` @@ -286,6 +269,45 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper `; } + #renderEditContentAction() { + return html` ${this._showContentEdit && this._workspaceEditContentPath + ? html` + + ${this._contentInvalid + ? html`!` + : nothing} + ` + : nothing}`; + } + + #renderEditSettingsAction() { + return html` + ${this._hasSettings && this._workspaceEditSettingsPath + ? html` + + ${this._settingsInvalid + ? html`!` + : nothing} + ` + : nothing} + `; + } + + #renderDeleteAction() { + if (this.readonly) return nothing; + return html` this.#context.requestDelete()}> + + `; + } + override render() { return this.#renderBlock(); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/manifests.ts index c3d07105cc..e734bfaad1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/manifests.ts @@ -14,6 +14,7 @@ export const manifests: Array = [ propertyEditorSchemaAlias: UMB_BLOCK_LIST_PROPERTY_EDITOR_ALIAS, icon: 'icon-thumbnail-list', group: 'lists', + supportsReadOnly: true, settings: { properties: [ { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts index 2d60a0d689..5aa7bfe843 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts @@ -4,7 +4,7 @@ import type { UmbBlockListLayoutModel, UmbBlockListValueModel } from '../../type import type { UmbBlockListEntryElement } from '../../components/block-list-entry/index.js'; import { UMB_BLOCK_LIST_PROPERTY_EDITOR_ALIAS } from './manifests.js'; import { UmbLitElement, umbDestroyOnDisconnect } from '@umbraco-cms/backoffice/lit-element'; -import { html, customElement, property, state, repeat, css } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, property, state, repeat, css, nothing } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UmbPropertyEditorUiElement, UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/extension-registry'; import { @@ -113,6 +113,27 @@ export class UmbPropertyEditorUIBlockListElement } } + /** + * Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content. + * @type {boolean} + * @attr + * @default false + */ + @property({ type: Boolean, reflect: true }) + public get readonly() { + return this.#readonly; + } + public set readonly(value) { + this.#readonly = value; + + if (this.#readonly) { + this.#sorter.disable(); + } else { + this.#sorter.enable(); + } + } + #readonly = false; + @state() private _limitMin?: number; @state() @@ -205,6 +226,32 @@ export class UmbPropertyEditorUIBlockListElement } override render() { + return html` ${repeat( + this._layouts, + (x) => x.contentUdi, + (layoutEntry, index) => html` + ${this.#renderInlineCreateButton(index)} + + + `, + )} + ${this.#renderCreateButton()} ${this.#renderPasteButton()} `; + } + + #renderInlineCreateButton(index: number) { + if (this.readonly) return nothing; + return html``; + } + + #renderCreateButton() { + if (this.readonly) return nothing; + let createPath: string | undefined; if (this._blocks?.length === 1) { const elementKey = this._blocks[0].contentElementTypeKey; @@ -213,28 +260,22 @@ export class UmbPropertyEditorUIBlockListElement } else { createPath = this._catalogueRouteBuilder?.({ view: 'create', index: -1 }); } - return html` ${repeat( - this._layouts, - (x) => x.contentUdi, - (layoutEntry, index) => - html` - - `, - )} - - - - - - `; + + return html` + + `; + } + + #renderPasteButton() { + if (this.readonly) return nothing; + return html` + + + + `; } static override styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts index df113d9b81..f44da55be5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts @@ -139,6 +139,9 @@ export abstract class UmbBlockManagerContext< this.#contentTypes.appendOne(data); } + getContentTypeKeyOfContentUdi(contentUdi: string) { + return this.getContentOf(contentUdi)?.contentTypeKey; + } contentTypeOf(contentTypeKey: string) { return this.#contentTypes.asObservablePart((source) => source.find((x) => x.unique === contentTypeKey)); } @@ -148,9 +151,6 @@ export abstract class UmbBlockManagerContext< getContentTypeNameOf(contentTypeKey: string) { return this.#contentTypes.getValue().find((x) => x.unique === contentTypeKey)?.name; } - getContentTypeKeyOf(contentTypeKey: string) { - return this.#contentTypes.getValue().find((x) => x.unique === contentTypeKey)?.unique; - } getContentTypeHasProperties(contentTypeKey: string) { const properties = this.#contentTypes.getValue().find((x) => x.unique === contentTypeKey)?.properties; return properties ? properties.length > 0 : false; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-property-dataset.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-property-dataset.context.ts index c7451f7806..4db36b5497 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-property-dataset.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-property-dataset.context.ts @@ -1,5 +1,6 @@ import { UMB_BLOCK_ELEMENT_PROPERTY_DATASET_CONTEXT } from './block-element-property-dataset.context-token.js'; import type { UmbBlockElementManager } from './block-element-manager.js'; +import { UMB_BLOCK_WORKSPACE_CONTEXT } from './block-workspace.context-token.js'; import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property'; import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @@ -36,6 +37,16 @@ export class UmbBlockElementPropertyDatasetContext extends UmbControllerBase imp super(host, UMB_PROPERTY_DATASET_CONTEXT.toString()); this.#elementManager = elementManager; + this.consumeContext(UMB_BLOCK_WORKSPACE_CONTEXT, (workspace) => { + this.observe( + workspace.readOnlyState.isOn, + (value) => { + this.#currentVariantCultureIsReadOnly.setValue(value); + }, + 'umbObserveReadOnlyStates', + ); + }); + this.provideContext(UMB_BLOCK_ELEMENT_PROPERTY_DATASET_CONTEXT, this); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts index 9ff8c1d3ad..19e304d8db 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts @@ -10,7 +10,7 @@ import { UmbClassState, UmbObjectState, UmbStringState } from '@umbraco-cms/back import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { ManifestWorkspace } from '@umbraco-cms/backoffice/extension-registry'; import { UMB_MODAL_CONTEXT, type UmbModalContext } from '@umbraco-cms/backoffice/modal'; -import { decodeFilePath } from '@umbraco-cms/backoffice/utils'; +import { decodeFilePath, UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; import { UMB_BLOCK_ENTRIES_CONTEXT, UMB_BLOCK_MANAGER_CONTEXT, @@ -60,6 +60,8 @@ export class UmbBlockWorkspaceContext(undefined); readonly variantId = this.#variantId.asObservable(); + public readonly readOnlyState = new UmbReadOnlyVariantStateManager(this); + constructor(host: UmbControllerHost, workspaceArgs: { manifest: ManifestWorkspace }) { super(host, workspaceArgs.manifest.alias); const manifest = workspaceArgs.manifest; @@ -92,6 +94,25 @@ export class UmbBlockWorkspaceContext { this.#variantId.setValue(variantId); }); + + // If the current property is readonly all inner block content should also be readonly. + this.observe(context.isReadOnly, (isReadOnly) => { + const unique = 'UMB_PROPERTY_CONTEXT'; + const variantId = this.#variantId.getValue(); + if (variantId === undefined) return; + + if (isReadOnly) { + const state = { + unique, + variantId, + message: '', + }; + + this.readOnlyState?.addState(state); + } else { + this.readOnlyState?.removeState(unique); + } + }); }); this.observe(this.variantId, (variantId) => { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/state-manager/state.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/state-manager/state.manager.ts index af6e8247c5..890f31b6e2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/state-manager/state.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/state-manager/state.manager.ts @@ -76,6 +76,15 @@ export class UmbStateManager extends UmbC this._states.setValue(this._states.getValue().filter((x) => !uniques.includes(x.unique))); } + /** + * Get all states from the state manager + * @returns {StateType[]} {StateType[]} All states in the state manager + * @memberof UmbStateManager + */ + getStates() { + return this._states.getValue(); + } + /** * Clear all states from the state manager * @memberof UmbStateManager diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts index e4f45a5518..e3b13b79be 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/mixins/form-control.mixin.ts @@ -165,6 +165,9 @@ export function UmbFormControlMixin< this._internals = this.attachInternals(); this.addEventListener('blur', () => { + /*if (e.composedPath().some((x) => x === this)) { + return; + }*/ this.pristine = false; this.checkValidity(); }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/components/data-type-flow-input/data-type-flow-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/components/data-type-flow-input/data-type-flow-input.element.ts index e6579bf1f6..df1cc59049 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/components/data-type-flow-input/data-type-flow-input.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/components/data-type-flow-input/data-type-flow-input.element.ts @@ -101,7 +101,7 @@ export class UmbInputDataTypeElement extends UmbFormControlMixin(UmbLitElement, label="Select Property Editor" look="placeholder" color="default" - @focus=${() => { + @blur=${() => { this.pristine = false; }} .href=${this._createRoute}> diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/document-publish-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/document-publish-modal.element.ts index 24cc03526e..f17353719b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/document-publish-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-modal/document-publish-modal.element.ts @@ -62,7 +62,8 @@ export class UmbDocumentPublishModalElement extends UmbModalBaseElement<

+ .variantLanguageOptions=${this._options} + .pickableFilter=${this.data?.pickableFilter}>

All new variants will be saved.

diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/document-publish-with-descendants-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/document-publish-with-descendants-modal.element.ts index 23a3a49e2c..1c1899711c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/document-publish-with-descendants-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/publish-with-descendants-modal/document-publish-with-descendants-modal.element.ts @@ -82,7 +82,8 @@ export class UmbDocumentPublishWithDescendantsModalElement extends UmbModalBaseE + .variantLanguageOptions=${this._options} + .pickableFilter=${this.data?.pickableFilter}> + .variantLanguageOptions=${this._options} + .pickableFilter=${this.data?.pickableFilter}>
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/document-schedule-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/document-schedule-modal.element.ts index aa4b3e1575..643285640c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/document-schedule-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/schedule-modal/document-schedule-modal.element.ts @@ -101,21 +101,28 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement< return repeat( this._options, (option) => option.unique, - (option) => html` - this.#selectionManager.select(option.unique)} - @deselected=${() => this.#selectionManager.deselect(option.unique)} - ?selected=${this.#isSelected(option.unique)}> - - ${UmbDocumentVariantLanguagePickerElement.renderLabel(option)} - - ${when(this.#isSelected(option.unique), () => this.#renderPublishDateInput(option))} - `, + (option) => this.#renderItem(option), ); } + #renderItem(option: UmbDocumentVariantOptionModel) { + const pickable = this.data?.pickableFilter ? this.data.pickableFilter(option) : () => true; + + return html` + this.#selectionManager.select(option.unique)} + @deselected=${() => this.#selectionManager.deselect(option.unique)} + ?selected=${this.#isSelected(option.unique)}> + + ${UmbDocumentVariantLanguagePickerElement.renderLabel(option)} + + ${when(this.#isSelected(option.unique), () => this.#renderPublishDateInput(option))} + `; + } + #renderPublishDateInput(option: UmbDocumentVariantOptionModel) { return html`
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/shared/document-variant-language-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/shared/document-variant-language-picker.element.ts index de6c4da99f..52d69df997 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/shared/document-variant-language-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/shared/document-variant-language-picker.element.ts @@ -29,33 +29,45 @@ export class UmbDocumentVariantLanguagePickerElement extends UmbLitElement { @state() _selection: Array = []; + /** + * A filter function that determines if an item is pickableFilter or not. + * @memberof UmbDocumentVariantLanguagePickerElement + * @returns {boolean} - True if the item is pickableFilter, false otherwise. + */ + @property({ attribute: false }) + public pickableFilter?: (item: UmbDocumentVariantOptionModel) => boolean; + override render() { return this.variantLanguageOptions.length ? repeat( this.variantLanguageOptions, (option) => option.unique, - (option) => html` - this.selectionManager.select(option.unique)} - @deselected=${() => this.selectionManager.deselect(option.unique)} - ?selected=${this._selection.includes(option.unique)}> - - ${UmbDocumentVariantLanguagePickerElement.renderLabel(option)} - - `, + (option) => html` ${this.#renderItem(option)} `, ) : html` There are no available variants `; } + #renderItem(option: UmbDocumentVariantOptionModel) { + const pickable = this.pickableFilter ? this.pickableFilter(option) : () => true; + return html` + this.selectionManager.select(option.unique)} + @deselected=${() => this.selectionManager.deselect(option.unique)} + ?selected=${this._selection.includes(option.unique)}> + + ${UmbDocumentVariantLanguagePickerElement.renderLabel(option)} + + `; + } + static renderLabel(option: UmbDocumentVariantOptionModel) { return html`
- - ${option.variant?.segment ? option.variant.segment + ' - ' : ''}${option.variant?.name ?? option.language.name} - + ${option.language.name}
${UmbDocumentVariantLanguagePickerElement.renderVariantStatus(option)}
${option.language.isMandatory && option.variant?.state !== UmbDocumentVariantState.PUBLISHED ? html`
diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/types.ts index d131490619..d3e91cdf89 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/types.ts @@ -2,6 +2,7 @@ import type { UmbDocumentVariantOptionModel } from '../types.js'; export interface UmbDocumentVariantPickerData { options: Array; + pickableFilter?: (variantOption: UmbDocumentVariantOptionModel) => boolean; } export interface UmbDocumentVariantPickerValue { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/document-unpublish-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/document-unpublish-modal.element.ts index 6838d56fa3..141b7cfbc6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/document-unpublish-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/modals/unpublish-modal/document-unpublish-modal.element.ts @@ -101,7 +101,8 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement< + .variantLanguageOptions=${this._options} + .pickableFilter=${this.data?.pickableFilter}>

diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 9186d6db4b..74b0629683 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -25,7 +25,7 @@ import { } from '../paths.js'; import { UMB_DOCUMENTS_SECTION_PATH } from '../../section/paths.js'; import { UmbDocumentPreviewRepository } from '../repository/preview/index.js'; -import { sortVariants } from '../utils.js'; + import { UMB_DOCUMENT_WORKSPACE_ALIAS } from './manifests.js'; import { UmbEntityContext } from '@umbraco-cms/backoffice/entity'; import { UMB_INVARIANT_CULTURE, UmbVariantId } from '@umbraco-cms/backoffice/variant'; @@ -68,6 +68,7 @@ import type { UmbContentWorkspaceContext } from '@umbraco-cms/backoffice/content import type { UmbDocumentTypeDetailModel } from '@umbraco-cms/backoffice/document-type'; import { UmbIsTrashedEntityContext } from '@umbraco-cms/backoffice/recycle-bin'; import { UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; +import { sortVariants } from '../utils.js'; type EntityType = UmbDocumentDetailModel; export class UmbDocumentWorkspaceContext @@ -104,7 +105,7 @@ export class UmbDocumentWorkspaceContext /*#blueprint = new UmbObjectState(undefined); public readonly blueprint = this.#blueprint.asObservable();*/ - readOnlyState = new UmbReadOnlyVariantStateManager(this); + public readOnlyState = new UmbReadOnlyVariantStateManager(this); public isLoaded() { return this.#getDataPromise; @@ -637,6 +638,11 @@ export class UmbDocumentWorkspaceContext } } + #readOnlyLanguageVariantsFilter = (option: UmbDocumentVariantOptionModel) => { + const readOnlyCultures = this.readOnlyState.getStates().map((s) => s.variantId.culture); + return readOnlyCultures.includes(option.culture) === false; + }; + async #handleSaveAndPreview() { const unique = this.getUnique(); if (!unique) throw new Error('Unique is missing'); @@ -681,6 +687,7 @@ export class UmbDocumentWorkspaceContext .open(this, UMB_DOCUMENT_PUBLISH_MODAL, { data: { options, + pickableFilter: this.#readOnlyLanguageVariantsFilter, }, value: { selection: selected }, }) @@ -786,6 +793,7 @@ export class UmbDocumentWorkspaceContext .open(this, UMB_DOCUMENT_SAVE_MODAL, { data: { options, + pickableFilter: this.#readOnlyLanguageVariantsFilter, }, value: { selection: selected }, }) @@ -833,6 +841,7 @@ export class UmbDocumentWorkspaceContext .open(this, UMB_DOCUMENT_SCHEDULE_MODAL, { data: { options, + pickableFilter: this.#readOnlyLanguageVariantsFilter, }, value: { selection: selected.map((unique) => ({ unique, schedule: {} })) }, }) @@ -873,6 +882,7 @@ export class UmbDocumentWorkspaceContext .open(this, UMB_DOCUMENT_PUBLISH_WITH_DESCENDANTS_MODAL, { data: { options, + pickableFilter: this.#readOnlyLanguageVariantsFilter, }, value: { selection: selected }, })