Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: block grid block type area/root validation #18224

Open
wants to merge 12 commits into
base: v15/dev
Choose a base branch
from
2 changes: 2 additions & 0 deletions src/Umbraco.Web.UI.Client/src/assets/lang/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2619,6 +2619,8 @@ export default {
labelInlineMode: 'Display inline with text',
notExposedLabel: 'Draft',
notExposedDescription: 'This Block is not yet created for this variant',
areaValidationEntriesNotAllowed: '<strong>%0%</strong> is not allowed in this area.',
rootValidationEntriesNotAllowed: '<strong>%0%</strong> is not allowed in the root of this property.',
unsupportedBlockName: 'Unsupported',
unsupportedBlockDescription:
'This content is no longer supported in this Editor. If you are missing this content, please contact your administrator. Otherwise delete it.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ const SORTER_CONFIG: UmbSorterConfig<UmbBlockGridLayoutModel, UmbBlockGridEntryE
resolvePlacement: resolvePlacementAsBlockGrid,
identifier: 'block-grid-editor',
itemSelector: 'umb-block-grid-entry',
disabledItemSelector: 'umb-block-grid-entry[unsupported]',
containerSelector: '.umb-block-grid__layout-container',
};

Expand Down Expand Up @@ -339,6 +338,7 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen
this.#typeLimitValidator = undefined;
}
if (hasTypeLimits) {
// If we have specific block type limits, we should use those for validation (not the Block Type Configurations)
this.#typeLimitValidator = this.addValidator(
'customError',
() => {
Expand All @@ -361,6 +361,28 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen
return !this.#context.checkBlockTypeLimitsValidity();
},
);
} else {
// Limit based on Block Type Configurations (Allow in Areas / allow in root)
this.#typeLimitValidator = this.addValidator(
'customError',
() => {
const invalids = this.#context
.getInvalidBlockTypeConfigurations()
// make invalids unique:
.filter((v, i, a) => a.indexOf(v) === i)
// join them together to become a string:
.join(', ');
return this.localize.term(
this._areaKey
? 'blockEditor_areaValidationEntriesNotAllowed'
: 'blockEditor_rootValidationEntriesNotAllowed',
invalids,
);
},
() => {
return !this.#context.checkBlockTypeConfigurationValidity();
},
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
@state()
_exposed?: boolean;

// Unuspported is triggerede if the Block Type is not reconized, it can also be triggerede by the Content Element Type not existing any longer. [NL]
@state()
_unsupported?: boolean;

Expand All @@ -107,14 +108,17 @@

@state()
_canScale?: boolean;

@state()
_showInlineCreateBefore?: boolean;
@state()
_showInlineCreateAfter?: boolean;
@state()
_inlineCreateAboveWidth?: string;

// If the Block Type is disallowed in this location then it become a invalid Block Type. Notice not supported blocks are determined via the unsupported property. [NL]
@property({ type: Boolean, attribute: 'location-invalid', reflect: true })
_invalidLocation?: boolean;

// 'content-invalid' attribute is used for styling purpose.
@property({ type: Boolean, attribute: 'content-invalid', reflect: true })
_contentInvalid?: boolean;
Expand Down Expand Up @@ -166,6 +170,13 @@
},
null,
);
this.observe(
this.#context.isAllowed,
(isAllowed) => {
this._invalidLocation = !isAllowed;
},
null,
);

Check warning on line 179 in src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v15/dev)

❌ Getting worse: Large Method

UmbBlockGridEntryElement.init increases from 134 to 141 lines of code, threshold = 70. Large functions with many lines of code are generally harder to understand and lower the code health. Avoid adding more lines to this function.
this.observe(
this.#context.blockType,
(blockType) => {
Expand Down Expand Up @@ -452,6 +463,11 @@
${!this._showContentEdit && this._contentInvalid
? html`<uui-badge attention color="danger" label="Invalid content">!</uui-badge>`
: nothing}
${this._invalidLocation
? html`<uui-tag id="invalidLocation" color="danger"
><umb-localize key="blockEditor_invalidDropPosition" .args=${[this._label]}></umb-localize
></uui-tag>`
: nothing}
${this._canScale
? html` <umb-block-scale-handler
@mousedown=${(e: MouseEvent) => this.#context.scaleManager.onScaleMouseDown(e)}>
Expand Down Expand Up @@ -624,11 +640,19 @@
transition: border-color 240ms ease-in;
}

:host([location-invalid])::after,
:host([settings-invalid])::after,
:host([content-invalid])::after {
border-color: var(--uui-color-danger);
}

#invalidLocation {
position: absolute;
top: -1em;
left: var(--uui-size-space-2);
z-index: 2;
}

uui-action-bar {
position: absolute;
top: var(--uui-size-2);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { UmbBlockDataModel } from '../../block/index.js';

Check notice on line 1 in src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v15/dev)

✅ Getting better: Overall Code Complexity

The mean cyclomatic complexity decreases from 4.53 to 4.42, threshold = 4. This file has many conditional statements (e.g. if, for, while) across its implementation, leading to lower code health. Avoid adding more conditionals.
import { UMB_BLOCK_CATALOGUE_MODAL, UmbBlockEntriesContext } from '../../block/index.js';
import {
UMB_BLOCK_GRID_ENTRY_CONTEXT,
Expand Down Expand Up @@ -40,6 +40,7 @@
minRequirement: number;
maxRequirement: number;
}

export class UmbBlockGridEntriesContext
extends UmbBlockEntriesContext<
typeof UMB_BLOCK_GRID_MANAGER_CONTEXT,
Expand Down Expand Up @@ -409,6 +410,12 @@
return this._catalogueRouteBuilderState.getValue()?.({ view: 'clipboard', index: index });
}

isBlockTypeAllowed(contentTypeKey: string) {
return this.#allowedBlockTypes.asObservablePart((types) =>
types.some((x) => x.contentElementTypeKey === contentTypeKey),
);
}

/*
async setLayouts(layouts: Array<UmbBlockGridLayoutModel>) {
await this._retrieveManager;
Expand Down Expand Up @@ -559,6 +566,7 @@
}
}

// Property to hold the result of the check, used to make a meaningful Validation Message
#invalidBlockTypeLimits?: Array<UmbBlockGridAreaTypeInvalidRuleType>;

getInvalidBlockTypeLimits() {
Expand Down Expand Up @@ -625,8 +633,48 @@
return undefined;
})
.filter((x) => x !== undefined) as Array<UmbBlockGridAreaTypeInvalidRuleType>;
const hasInvalidRules = this.#invalidBlockTypeLimits.length > 0;
return hasInvalidRules === false;
return this.#invalidBlockTypeLimits.length === 0;
}

#invalidBlockTypeConfigurations?: Array<string>;

getInvalidBlockTypeConfigurations() {
return this.#invalidBlockTypeConfigurations ?? [];
}
/**
* @internal
* @returns {boolean} - True if the block type limits are valid, otherwise false.
*/
checkBlockTypeConfigurationValidity(): boolean {
this.#invalidBlockTypeConfigurations = [];

const layoutEntries = this._layoutEntries.getValue();
if (layoutEntries.length === 0) return true;

// Check all layout entries if they are allowed.
const allowedBlocks = this.#allowedBlockTypes.getValue();
if (allowedBlocks.length === 0) return false;

const allowedKeys = allowedBlocks.map((x) => x.contentElementTypeKey);
// get content for each layout entry:
const invalidEntries = layoutEntries.filter((entry) => {
const contentTypeKey = this._manager!.getContentTypeKeyOfContentKey(entry.contentKey);
if (!contentTypeKey) {
// We could not find the content type key, so we cant determin if this is valid or not when the content is missing.
// This should be captured elsewhere as the Block then becomes invalid. So the unsupported Block should capture this.
return false;
}
const isBad = allowedKeys.indexOf(contentTypeKey) === -1;
if (contentTypeKey && isBad) {
// if bad, then add the ContentTypeName to the list of invalids (if we could not find the name add the key)
this.#invalidBlockTypeConfigurations?.push(
this._manager?.getContentTypeNameOf(contentTypeKey) ?? contentTypeKey,
);
}
return isBad;
});

return invalidEntries.length === 0;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@
#canScale = new UmbBooleanState(false);
readonly canScale = this.#canScale.asObservable();

#isAllowed = new UmbBooleanState(false);
readonly isAllowed = this.#isAllowed.asObservable();

#areaGridColumns = new UmbNumberState(undefined);
readonly areaGridColumns = this.#areaGridColumns.asObservable();

Expand Down Expand Up @@ -158,6 +161,21 @@

this.#gotEntriesAndManager();

this.observe(
this.contentElementTypeKey,
(key) => {
this.observe(
key ? this._entries?.isBlockTypeAllowed(key) : undefined,
(isAllowed) => {
if (isAllowed === undefined) return;
this.#isAllowed.setValue(isAllowed);
},
'observeIsAllowed',
);
},
'observeContentElementTypeKey',
);

Check warning on line 178 in src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entry.context.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v15/dev)

❌ New issue: Complex Method

UmbBlockGridEntryContext._gotEntries has a cyclomatic complexity of 11, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
// Retrieve scale options:
this.observe(
observeMultiple([this.minMaxRowSpan, this.columnSpanOptions, this._entries.layoutColumns]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -498,17 +498,14 @@ export abstract class UmbBlockEntryContext<
#gotEntries() {
this.#updateCreatePaths();
this.#observeLayout();
if (this._entries) {
this.observe(
this._entries.workspacePath,
(workspacePath) => {
this.#workspacePath.setValue(workspacePath);
},
'observeWorkspacePath',
);
} else {
this.removeUmbControllerByAlias('observeWorkspacePath');
}

this.observe(
this._entries?.workspacePath,
(workspacePath) => {
this.#workspacePath.setValue(workspacePath);
},
'observeWorkspacePath',
);
}

abstract _gotEntries(): void;
Expand All @@ -521,6 +518,7 @@ export abstract class UmbBlockEntryContext<
this._manager.contentOf(this.#contentKey),
(content) => {
if (this.#unsupported.getValue() !== true) {
// If we could not find content, then we do not know the contentTypeKey and then the content is broken. [NL]
this.#unsupported.setValue(!content);
}
this.#content.setValue(content);
Expand Down Expand Up @@ -556,7 +554,7 @@ export abstract class UmbBlockEntryContext<
throw new Error('No contentStructure found');
}

// observe blockType:
// observe variantId:
this.observe(
observeMultiple([
this._manager.variantId,
Expand All @@ -568,7 +566,7 @@ export abstract class UmbBlockEntryContext<
this.#variantId.setValue(variantId.toVariant(variesByCulture, variesBySegment));
this.#gotVariantId();
},
'observeBlockType',
'observeVariantId',
);
}

Expand Down Expand Up @@ -607,7 +605,10 @@ export abstract class UmbBlockEntryContext<
this.#contentStructure = this._manager.getStructure(contentTypeKey);
this.#contentStructurePromiseResolve?.();

this.#unsupported.setValue(!this.#contentStructure);
if (!this.#contentStructure) {
// If we got no content structure, then this is element type did not load and there for it is not supported any longer.
this.#unsupported.setValue(true);
}

this.observe(
this.#contentStructure?.ownerContentType,
Expand Down Expand Up @@ -649,6 +650,10 @@ export abstract class UmbBlockEntryContext<
this._manager.blockTypeOf(contentTypeKey),
(blockType) => {
this._blockType.setValue(blockType as BlockType);
if (!blockType) {
// If the block type is undefined, then we do not have this Block Type and the Block is then unsupported. [NL]
this.#unsupported.setValue(true);
}
},
'observeBlockType',
);
Expand Down
Loading