Fix: Block Editors — use create labels and hide 'edit'-action if no properties (#2424)

use create labels and hide edit if no properties

Co-authored-by: Mads Rasmussen <madsr@hey.com>
Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
This commit is contained in:
Niels Lyngsø
2024-10-11 08:32:48 +02:00
committed by Jacob Overgaard
parent 11f69be335
commit 0d61ff3411
16 changed files with 125 additions and 49 deletions

View File

@@ -16,7 +16,7 @@ export interface UmbBlockEditorCustomViewProperties<
BlockType extends UmbBlockTypeBaseModel = UmbBlockTypeBaseModel,
> {
manifest?: ManifestBlockEditorCustomView;
config?: Partial<UmbBlockEditorCustomViewConfiguration>;
config?: UmbBlockEditorCustomViewConfiguration;
blockType?: BlockType;
contentKey?: string;
label?: string;

View File

@@ -49,7 +49,9 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement {
}
override render() {
return html`<umb-ref-grid-block standalone href=${this.config?.editContentPath ?? ''}>
return html`<umb-ref-grid-block
standalone
href=${(this.config?.showContentEdit ? this.config?.editContentPath : undefined) ?? ''}>
<umb-icon slot="icon" .name=${this.icon}></umb-icon>
<umb-ufm-render slot="name" inline .markdown=${this.label} .value=${this.content}></umb-ufm-render>
<umb-property-type-based-property

View File

@@ -29,7 +29,9 @@ export class UmbBlockGridBlockElement extends UmbLitElement {
content?: UmbBlockDataType;
override render() {
return html`<umb-ref-grid-block standalone href=${this.config?.editContentPath ?? ''}>
return html`<umb-ref-grid-block
standalone
href=${(this.config?.showContentEdit ? this.config?.editContentPath : undefined) ?? ''}>
<umb-icon slot="icon" .name=${this.icon}></umb-icon>
<umb-ufm-render slot="name" inline .markdown=${this.label} .value=${this.content}></umb-ufm-render>
<umb-block-grid-areas-container slot="areas"></umb-block-grid-areas-container>

View File

@@ -171,7 +171,10 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen
private _canCreate?: boolean;
@state()
private _singleBlockTypeName?: string;
private _createLabel?: string;
@state()
private _configCreateLabel?: string;
@state()
private _styleElement?: HTMLLinkElement;
@@ -201,12 +204,13 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen
this.observe(
this.#context.firstAllowedBlockTypeName(),
(firstAllowedName) => {
this._singleBlockTypeName = firstAllowedName;
this._createLabel = this.localize.term('blockEditor_addThis', [firstAllowedName]);
},
'observeSingleBlockTypeName',
);
} else {
this.removeUmbControllerByAlias('observeSingleBlockTypeName');
this._createLabel = this.localize.term('blockEditor_addBlock');
}
},
null,
@@ -239,6 +243,20 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen
},
'observeStylesheet',
);
if (this.areaKey) {
this.observe(
this.#context.areaTypeCreateLabel,
(label) => (this._configCreateLabel = label),
'observeConfigCreateLabel',
);
} else {
this.observe(
manager.editorConfigurationPart((x) => x?.find((y) => y.alias === 'createLabel')?.value),
(label) => (this._configCreateLabel = label as string | undefined),
'observeConfigCreateLabel',
);
}
});
new UmbFormControlValidator(this, this /*, this.#dataPath*/);
@@ -340,9 +358,7 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen
return html`<uui-button-group id="createButton">
<uui-button
look="placeholder"
label=${this._singleBlockTypeName
? this.localize.term('blockEditor_addThis', [this._singleBlockTypeName])
: this.localize.term('blockEditor_addBlock')}
label=${this._configCreateLabel ?? this._createLabel ?? ''}
href=${this.#context.getPathForCreateBlock(-1) ?? ''}></uui-button>
${this._areaKey === null
? html` <uui-button

View File

@@ -133,7 +133,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
this.#context.showContentEdit,
(showContentEdit) => {
this._showContentEdit = showContentEdit;
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config, showContentEdit } });
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config!, showContentEdit } });
},
null,
);
@@ -141,7 +141,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
this.#context.settingsElementTypeKey,
(key) => {
this._hasSettings = !!key;
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config, showSettingsEdit: !!key } });
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config!, showSettingsEdit: !!key } });
},
null,
);
@@ -245,7 +245,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
this.#context.workspaceEditContentPath,
(path) => {
this._workspaceEditContentPath = path;
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config, editContentPath: path } });
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config!, editContentPath: path } });
},
null,
);
@@ -253,7 +253,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
this.#context.workspaceEditSettingsPath,
(path) => {
this._workspaceEditSettingsPath = path;
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config, editSettingsPath: path } });
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config!, editSettingsPath: path } });
},
null,
);
@@ -388,6 +388,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
.label=${this._label}
.icon=${this._icon}
.unpublished=${!this._exposed}
.config=${this._blockViewProps.config}
.content=${this._blockViewProps.content}
.settings=${this._blockViewProps.settings}></umb-block-grid-block-inline>`;
}
@@ -398,6 +399,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
.label=${this._label}
.icon=${this._icon}
.unpublished=${!this._exposed}
.config=${this._blockViewProps.config}
.content=${this._blockViewProps.content}
.settings=${this._blockViewProps.settings}></umb-block-grid-block>`;
}

View File

@@ -51,7 +51,9 @@ export class UmbBlockGridEntriesContext
#layoutColumns = new UmbNumberState(undefined);
readonly layoutColumns = this.#layoutColumns.asObservable();
#areaType?: UmbBlockGridTypeAreaType;
#areaType = new UmbObjectState<UmbBlockGridTypeAreaType | undefined>(undefined);
areaType = this.#areaType.asObservable();
areaTypeCreateLabel = this.#areaType.asObservablePart((x) => x?.createLabel);
#parentUnique?: string | null;
#areaKey?: string | null;
@@ -117,14 +119,14 @@ export class UmbBlockGridEntriesContext
getMinAllowed() {
if (this.#areaKey) {
return this.#areaType?.minAllowed ?? 0;
return this.#areaType.getValue()?.minAllowed ?? 0;
}
return this._manager?.getMinAllowed() ?? 0;
}
getMaxAllowed() {
if (this.#areaKey) {
return this.#areaType?.maxAllowed ?? Infinity;
return this.#areaType.getValue()?.maxAllowed ?? Infinity;
}
return this._manager?.getMaxAllowed() ?? Infinity;
}
@@ -147,15 +149,16 @@ export class UmbBlockGridEntriesContext
.addUniquePaths(['propertyAlias', 'variantId', 'parentUnique', 'areaKey'])
.addAdditionalPath(':view/:index')
.onSetup((routingInfo) => {
if (!this._manager) return false;
// Idea: Maybe on setup should be async, so it can retrieve the values when needed? [NL]
const index = routingInfo.index ? parseInt(routingInfo.index) : -1;
return {
data: {
blocks: this.#allowedBlockTypes.getValue(),
blockGroups: this._manager?.getBlockGroups() ?? [],
blockGroups: this._manager.getBlockGroups() ?? [],
openClipboard: routingInfo.view === 'clipboard',
originData: { index: index, areaKey: this.#areaKey, parentUnique: this.#parentUnique },
createBlockInWorkspace: true,
createBlockInWorkspace: this._manager.getInlineEditingMode() === false,
},
};
})
@@ -301,7 +304,7 @@ export class UmbBlockGridEntriesContext
this.observe(
this.#parentEntry.areaType(this.#areaKey),
(areaType) => {
this.#areaType = areaType;
this.#areaType.setValue(areaType);
const hostEl = this.getHostElement() as HTMLElement | undefined;
if (!hostEl) return;
hostEl.setAttribute('data-area-alias', areaType?.alias ?? '');
@@ -327,13 +330,14 @@ export class UmbBlockGridEntriesContext
if (!this._manager) return;
//const range = this.#retrieveRangeLimits();
if (this.#areaKey != null) {
const areaType = this.#areaType.getValue();
this.removeUmbControllerByAlias('observeConfigurationRootLimits');
// Area entries:
if (!this.#areaType) return undefined;
if (!areaType) return undefined;
// No need to observe as this method is called every time the area is changed.
this.#rangeLimits.setValue({
min: this.#areaType.minAllowed ?? 0,
max: this.#areaType.maxAllowed ?? Infinity,
min: areaType.minAllowed ?? 0,
max: areaType.maxAllowed ?? Infinity,
});
} else if (this.#areaKey === null) {
if (!this._manager) return undefined;
@@ -402,18 +406,19 @@ export class UmbBlockGridEntriesContext
/**
* @internal
* @returns an Array of ElementTypeKeys that are allowed in the current area. Or undefined if not ready jet.
* @returns {Array<UmbBlockGridTypeModel>} an Array of ElementTypeKeys that are allowed in the current area. Or undefined if not ready jet.
*/
#retrieveAllowedElementTypes() {
if (!this._manager) return [];
if (this.#areaKey) {
const areaType = this.#areaType.getValue();
// Area entries:
if (!this.#areaType) return [];
if (!areaType) return [];
if (this.#areaType.specifiedAllowance && this.#areaType.specifiedAllowance?.length > 0) {
if (areaType.specifiedAllowance && areaType.specifiedAllowance?.length > 0) {
return (
this.#areaType.specifiedAllowance
areaType.specifiedAllowance
.flatMap((permission) => {
if (permission.groupKey) {
return (
@@ -449,10 +454,11 @@ export class UmbBlockGridEntriesContext
if (!this._manager) return;
if (this.#areaKey) {
const areaType = this.#areaType.getValue();
// Area entries:
if (!this.#areaType) return;
if (!areaType) return;
if (this.#areaType.specifiedAllowance && this.#areaType.specifiedAllowance?.length > 0) {
if (areaType.specifiedAllowance && areaType.specifiedAllowance?.length > 0) {
this.#hasTypeLimits.setValue(true);
}
} else if (this.#areaKey === null) {
@@ -470,11 +476,12 @@ export class UmbBlockGridEntriesContext
* @returns {boolean} - True if the block type limits are valid, otherwise false.
*/
checkBlockTypeLimitsValidity(): boolean {
if (!this.#areaType || !this.#areaType.specifiedAllowance) return false;
const areaType = this.#areaType.getValue();
if (!areaType || !areaType.specifiedAllowance) return false;
const layoutEntries = this._layoutEntries.getValue();
this.#invalidBlockTypeLimits = this.#areaType.specifiedAllowance
this.#invalidBlockTypeLimits = areaType.specifiedAllowance
.map((rule) => {
const minAllowed = rule.minAllowed || 0;
const maxAllowed = rule.maxAllowed || 0;
@@ -531,7 +538,7 @@ export class UmbBlockGridEntriesContext
/**
* Check if given contentKey is allowed in the current area.
* @param contentKey {string} - The contentKey of the content to check.
* @param {string} contentKey - The contentKey of the content to check.
* @returns {boolean} - True if the content is allowed in the current area, otherwise false.
*/
allowDrop(contentKey: string) {

View File

@@ -11,6 +11,7 @@ import {
UmbNumberState,
UmbObjectState,
appendToFrozenArray,
mergeObservables,
observeMultiple,
} from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
@@ -39,6 +40,10 @@ export class UmbBlockGridEntryContext
readonly minMaxRowSpan = this._blockType.asObservablePart((x) =>
x ? [x.rowMinSpan ?? 1, x.rowMaxSpan ?? 1] : undefined,
);
readonly forceHideContentEditorInOverlay = this._blockType.asObservablePart((x) =>
x ? (x.forceHideContentEditorInOverlay ?? false) : undefined,
);
public getMinMaxRowSpan(): [number, number] | undefined {
const x = this._blockType.getValue();
if (!x) return undefined;
@@ -58,7 +63,10 @@ export class UmbBlockGridEntryContext
#areaGridColumns = new UmbNumberState(undefined);
readonly areaGridColumns = this.#areaGridColumns.asObservable();
readonly showContentEdit = this._blockType.asObservablePart((x) => !x?.forceHideContentEditorInOverlay);
readonly showContentEdit = mergeObservables(
[this._contentStructureHasProperties, this.forceHideContentEditorInOverlay],
([a, b]) => a === true && b === false,
);
#firstPropertyType = new UmbObjectState<UmbPropertyTypeModel | undefined>(undefined);
readonly firstPropertyType = this.#firstPropertyType.asObservable();

View File

@@ -1,6 +1,11 @@
import type { UmbBlockGridLayoutModel, UmbBlockGridTypeModel } from '../types.js';
import type { UmbBlockGridWorkspaceOriginData } from '../index.js';
import { UmbArrayState, appendToFrozenArray, pushAtToUniqueArray } from '@umbraco-cms/backoffice/observable-api';
import {
UmbArrayState,
UmbBooleanState,
appendToFrozenArray,
pushAtToUniqueArray,
} from '@umbraco-cms/backoffice/observable-api';
import { removeLastSlashFromPath, transformServerPathToClientPath } from '@umbraco-cms/backoffice/utils';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app';
@@ -18,6 +23,16 @@ export class UmbBlockGridManagerContext<
BlockLayoutType extends UmbBlockGridLayoutModel = UmbBlockGridLayoutModel,
> extends UmbBlockManagerContext<UmbBlockGridTypeModel, UmbBlockGridLayoutModel, UmbBlockGridWorkspaceOriginData> {
//
#inlineEditingMode = new UmbBooleanState(undefined);
readonly inlineEditingMode = this.#inlineEditingMode.asObservable();
setInlineEditingMode(inlineEditingMode: boolean | undefined) {
this.#inlineEditingMode.setValue(inlineEditingMode ?? false);
}
getInlineEditingMode(): boolean | undefined {
return this.#inlineEditingMode.getValue();
}
#initAppUrl: Promise<void>;
#appUrl?: string;
#blockGroups = new UmbArrayState(<Array<UmbBlockTypeGroup>>[], (x) => x.key);

View File

@@ -53,6 +53,9 @@ export class UmbPropertyEditorUIBlockGridElement
const blockGroups = config.getValueByAlias<Array<UmbBlockTypeGroup>>('blockGroups') ?? [];
this.#managerContext.setBlockGroups(blockGroups);
const useInlineEditingAsDefault = config.getValueByAlias<boolean>('useInlineEditingAsDefault');
this.#managerContext.setInlineEditingMode(useInlineEditingAsDefault);
this.style.maxWidth = config.getValueByAlias<string>('maxPropertyWidth') ?? '';
//config.useLiveEditing, is covered by the EditorConfiguration of context. [NL]

View File

@@ -29,6 +29,7 @@ export interface UmbBlockGridTypeAreaType {
minAllowed?: number;
maxAllowed?: number;
specifiedAllowance?: Array<UmbBlockGridTypeAreaTypePermission>;
createLabel?: string;
}
export interface UmbBlockGridTypeAreaTypePermission {

View File

@@ -111,7 +111,7 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper
this.#context.showContentEdit,
(showContentEdit) => {
this._showContentEdit = showContentEdit;
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config, showContentEdit } });
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config!, showContentEdit } });
},
null,
);
@@ -119,7 +119,7 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper
this.#context.settingsElementTypeKey,
(key) => {
this._hasSettings = !!key;
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config, showSettingsEdit: !!key } });
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config!, showSettingsEdit: !!key } });
},
null,
);
@@ -195,7 +195,7 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper
this.#context.workspaceEditContentPath,
(path) => {
this._workspaceEditContentPath = path;
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config, editContentPath: path } });
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config!, editContentPath: path } });
},
null,
);
@@ -203,7 +203,7 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper
this.#context.workspaceEditSettingsPath,
(path) => {
this._workspaceEditSettingsPath = path;
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config, editSettingsPath: path } });
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config!, editSettingsPath: path } });
},
null,
);

View File

@@ -11,14 +11,14 @@ export class UmbBlockListEntryContext extends UmbBlockEntryContext<
> {
#inlineEditingMode = new UmbBooleanState(undefined);
readonly inlineEditingMode = this.#inlineEditingMode.asObservable();
readonly forceHideContentEditorInOverlay = this._blockType.asObservablePart(
(x) => !!x?.forceHideContentEditorInOverlay,
readonly forceHideContentEditorInOverlay = this._blockType.asObservablePart((x) =>
x ? (x.forceHideContentEditorInOverlay ?? false) : undefined,
);
readonly showContentEdit = mergeObservables(
[this.forceHideContentEditorInOverlay, this.inlineEditingMode],
([forceHide, inlineMode]): boolean => {
return !forceHide && !inlineMode;
[this._contentStructureHasProperties, this.forceHideContentEditorInOverlay, this.inlineEditingMode],
([a, b, c]): boolean => {
return a === true && b === false && c === false;
},
);

View File

@@ -74,11 +74,11 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert
this.observe(this.#context.showContentEdit, (showContentEdit) => {
this._showContentEdit = showContentEdit;
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config, showContentEdit } });
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config!, showContentEdit } });
});
this.observe(this.#context.settingsElementTypeKey, (key) => {
this._hasSettings = !!key;
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config, showSettingsEdit: !!key } });
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config!, showSettingsEdit: !!key } });
});
this.observe(this.#context.contentElementTypeAlias, (alias) => {
this._contentElementTypeAlias = alias;
@@ -152,7 +152,7 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert
this.#context.workspaceEditContentPath,
(path) => {
this._workspaceEditContentPath = path;
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config, editContentPath: path } });
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config!, editContentPath: path } });
},
null,
);
@@ -160,7 +160,7 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert
this.#context.workspaceEditSettingsPath,
(path) => {
this._workspaceEditSettingsPath = path;
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config, editSettingsPath: path } });
this.#updateBlockViewProps({ config: { ...this._blockViewProps.config!, editSettingsPath: path } });
},
null,
);

View File

@@ -3,7 +3,7 @@ import { UMB_BLOCK_RTE_MANAGER_CONTEXT } from './block-rte-manager.context-token
import { UMB_BLOCK_RTE_ENTRIES_CONTEXT } from './block-rte-entries.context-token.js';
import { UmbBlockEntryContext } from '@umbraco-cms/backoffice/block';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { observeMultiple } from '@umbraco-cms/backoffice/observable-api';
import { mergeObservables, observeMultiple } from '@umbraco-cms/backoffice/observable-api';
export class UmbBlockRteEntryContext extends UmbBlockEntryContext<
typeof UMB_BLOCK_RTE_MANAGER_CONTEXT,
typeof UMB_BLOCK_RTE_MANAGER_CONTEXT.TYPE,
@@ -15,11 +15,16 @@ export class UmbBlockRteEntryContext extends UmbBlockEntryContext<
readonly displayInline = this._layout.asObservablePart((x) => (x ? (x.displayInline ?? false) : undefined));
readonly displayInlineConfig = this._blockType.asObservablePart((x) => (x ? (x.displayInline ?? false) : undefined));
readonly forceHideContentEditorInOverlay = this._blockType.asObservablePart(
(x) => !!x?.forceHideContentEditorInOverlay,
readonly forceHideContentEditorInOverlay = this._blockType.asObservablePart((x) =>
x ? (x.forceHideContentEditorInOverlay ?? false) : undefined,
);
readonly showContentEdit = this._blockType.asObservablePart((x) => !x?.forceHideContentEditorInOverlay);
readonly showContentEdit = mergeObservables(
[this._contentStructureHasProperties, this.forceHideContentEditorInOverlay],
([a, b]): boolean => {
return a === true && b === false;
},
);
constructor(host: UmbControllerHost) {
super(host, UMB_BLOCK_RTE_MANAGER_CONTEXT, UMB_BLOCK_RTE_ENTRIES_CONTEXT);

View File

@@ -120,6 +120,9 @@ export abstract class UmbBlockEntryContext<
};
});
#contentStructureHasProperties = new UmbBooleanState(undefined);
_contentStructureHasProperties = this.#contentStructureHasProperties.asObservable();
#settingsStructure?: UmbContentTypeStructureManager;
#settingsStructurePromiseResolve?: () => void;
#settingsStructurePromise = new Promise((resolve) => {
@@ -513,6 +516,14 @@ export abstract class UmbBlockEntryContext<
},
'observeContentElementType',
);
this.observe(
this.#contentStructure?.contentTypeHasProperties,
(has) => {
this.#contentStructureHasProperties.setValue(has);
},
'observeContentTypeHasProperties',
);
}
#getSettingsStructure(contentTypeKey: string | undefined) {

View File

@@ -8,6 +8,7 @@ import {
UmbBooleanState,
UmbClassState,
UmbStringState,
type MappingFunction,
mergeObservables,
} from '@umbraco-cms/backoffice/observable-api';
import { UmbDocumentTypeDetailRepository } from '@umbraco-cms/backoffice/document-type';
@@ -80,6 +81,9 @@ export abstract class UmbBlockManagerContext<
getEditorConfiguration(): UmbPropertyEditorConfigCollection | undefined {
return this._editorConfiguration.getValue();
}
editorConfigurationPart(method: MappingFunction<UmbPropertyEditorConfigCollection | undefined, unknown>) {
return this._editorConfiguration.asObservablePart(method);
}
setBlockTypes(blockTypes: Array<BlockType>) {
this.#blockTypes.setValue(blockTypes);