Block Area Rules

This commit is contained in:
Niels Lyngsø
2024-08-28 16:45:41 +02:00
parent 9518c9a53b
commit a8b416e5a8
4 changed files with 154 additions and 5 deletions

View File

@@ -135,6 +135,9 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen
#context = new UmbBlockGridEntriesContext(this);
#controlValidator?: UmbFormControlValidator;
#typeLimitValidator?: UmbFormControlValidatorConfig;
#rangeUnderflowValidator?: UmbFormControlValidatorConfig;
#rangeOverflowValidator?: UmbFormControlValidatorConfig;
@property({ type: String, attribute: 'area-key', reflect: true })
public set areaKey(value: string | null | undefined) {
@@ -217,6 +220,14 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen
null,
);
this.observe(
this.#context.hasTypeLimits,
(hasTypeLimits) => {
this.#setupBlockTypeLimitValidation(hasTypeLimits);
},
null,
);
this.#context.getManager().then((manager) => {
this.observe(
manager.layoutStylesheet,
@@ -233,8 +244,6 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen
new UmbFormControlValidator(this, this /*, this.#dataPath*/);
}
#rangeUnderflowValidator?: UmbFormControlValidatorConfig;
#rangeOverflowValidator?: UmbFormControlValidatorConfig;
async #setupRangeValidation(rangeLimit: UmbNumberRangeValueType | undefined) {
if (this.#rangeUnderflowValidator) {
this.removeValidator(this.#rangeUnderflowValidator);
@@ -277,6 +286,38 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen
}
}
async #setupBlockTypeLimitValidation(hasTypeLimits: boolean | undefined) {
if (this.#typeLimitValidator) {
this.removeValidator(this.#typeLimitValidator);
this.#typeLimitValidator = undefined;
}
if (hasTypeLimits) {
console.log('hasTypeLimits');
this.#typeLimitValidator = this.addValidator(
'patternMismatch',
() => {
const invalids = this.#context.getInvalidBlockTypeLimits();
return invalids
.map((invalidRule) =>
this.localize.term(
invalidRule.amount < invalidRule.minRequirement
? 'blockEditor_areaValidationEntriesShort'
: 'blockEditor_areaValidationEntriesExceed',
invalidRule.name,
invalidRule.amount,
invalidRule.minRequirement,
invalidRule.maxRequirement,
),
)
.join(', ');
},
() => {
return !this.#context.checkBlockTypeLimitsValidity();
},
);
}
}
// TODO: Missing ability to jump directly to creating a Block, when there is only one Block Type. [NL]
override render() {
return html`

View File

@@ -8,7 +8,13 @@ import {
import type { UmbBlockGridLayoutModel, UmbBlockGridTypeAreaType, UmbBlockGridTypeModel } from '../types.js';
import { UMB_BLOCK_GRID_MANAGER_CONTEXT } from './block-grid-manager.context-token.js';
import type { UmbBlockGridScalableContainerContext } from './block-grid-scale-manager/block-grid-scale-manager.controller.js';
import { UmbArrayState, UmbNumberState, UmbObjectState, UmbStringState } from '@umbraco-cms/backoffice/observable-api';
import {
UmbArrayState,
UmbBooleanState,
UmbNumberState,
UmbObjectState,
UmbStringState,
} from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
import { pathFolderName } from '@umbraco-cms/backoffice/utils';
@@ -49,6 +55,9 @@ export class UmbBlockGridEntriesContext
public readonly amountOfAllowedBlockTypes = this.#allowedBlockTypes.asObservablePart((x) => x.length);
public readonly canCreate = this.#allowedBlockTypes.asObservablePart((x) => x.length > 0);
#hasTypeLimits = new UmbBooleanState(undefined);
public readonly hasTypeLimits = this.#hasTypeLimits.asObservable();
firstAllowedBlockTypeName() {
if (!this._manager) {
throw new Error('Manager not ready');
@@ -241,8 +250,6 @@ export class UmbBlockGridEntriesContext
'observeThisLayouts',
);
this.removeUmbControllerByAlias('observeAreaType');
const hostEl = this.getHostElement() as HTMLElement | undefined;
if (hostEl) {
hostEl.removeAttribute('data-area-alias');
@@ -309,6 +316,7 @@ export class UmbBlockGridEntriesContext
#setupAllowedBlockTypes() {
if (!this._manager) return;
this.#allowedBlockTypes.setValue(this.#retrieveAllowedElementTypes());
this.#setupAllowedBlockTypesLimits();
}
#setupRangeLimits() {
if (!this._manager) return;
@@ -429,6 +437,100 @@ export class UmbBlockGridEntriesContext
return [];
}
/**
* @internal
*/
#setupAllowedBlockTypesLimits() {
if (!this._manager) return;
if (this.#areaKey) {
// Area entries:
if (!this.#areaType) return;
if (this.#areaType.specifiedAllowance && this.#areaType.specifiedAllowance?.length > 0) {
this.#hasTypeLimits.setValue(true);
}
} else if (this.#areaKey === null) {
// RESET
}
}
#invalidBlockTypeLimits?: Array<{
groupKey?: string;
key?: string;
name: string;
amount: number;
minRequirement: number;
maxRequirement: number;
}>;
getInvalidBlockTypeLimits() {
return this.#invalidBlockTypeLimits ?? [];
}
/**
* @internal
* @returns {boolean} - True if the block type limits are valid, otherwise false.
*/
checkBlockTypeLimitsValidity(): boolean {
if (!this.#areaType || !this.#areaType.specifiedAllowance) return false;
const layoutEntries = this._layoutEntries.getValue();
this.#invalidBlockTypeLimits = [];
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!.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;
}
}
// 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;
console.log('amount', amount);
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;
}
}
// Lets fail cause the rule was bad.
console.error('Invalid block type limit rule.', rule);
return false;
});
return hasInvalidRules === false;
}
/**
* Check if given contentUdi is allowed in the current area.
* @param contentUdi {string} - The contentUdi of the content to check.

View File

@@ -62,6 +62,9 @@ export class UmbBlockGridManagerContext<
getBlockGroups() {
return this.#blockGroups.value;
}
getBlockGroupName(unique: string) {
return this.#blockGroups.getValue().find((group) => group.key === unique)?.name;
}
constructor(host: UmbControllerHost) {
super(host);

View File

@@ -148,6 +148,9 @@ 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;