Merge pull request #2248 from umbraco/v14/feature/readonly-block-list

Feature: Readonly mode for Block List Property Editor UI
This commit is contained in:
Niels Lyngsø
2024-09-09 10:51:41 +02:00
committed by GitHub
5 changed files with 147 additions and 51 deletions

View File

@@ -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()}</umb-extension-slot
>
<uui-action-bar>
${this._showContentEdit && this._workspaceEditContentPath
? html`<uui-button
label="edit"
look="secondary"
color=${this._contentInvalid ? 'danger' : ''}
href=${this._workspaceEditContentPath}>
<uui-icon name="icon-edit"></uui-icon>
${this._contentInvalid
? html`<uui-badge attention color="danger" label="Invalid content">!</uui-badge>`
: nothing}
</uui-button>`
: nothing}
${this._hasSettings && this._workspaceEditSettingsPath
? html`<uui-button
label="Edit settings"
look="secondary"
color=${this._settingsInvalid ? 'danger' : ''}
href=${this._workspaceEditSettingsPath}>
<uui-icon name="icon-settings"></uui-icon>
${this._settingsInvalid
? html`<uui-badge attention color="danger" label="Invalid settings">!</uui-badge>`
: nothing}
</uui-button>`
: nothing}
<uui-button label="delete" look="secondary" @click=${() => this.#context.requestDelete()}>
<uui-icon name="icon-remove"></uui-icon>
</uui-button>
${this.#renderEditContentAction()} ${this.#renderEditSettingsAction()} ${this.#renderDeleteAction()}
</uui-action-bar>
${!this._showContentEdit && this._contentInvalid
? html`<uui-badge attention color="danger" label="Invalid content">!</uui-badge>`
@@ -286,6 +269,45 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper
`;
}
#renderEditContentAction() {
return html` ${this._showContentEdit && this._workspaceEditContentPath
? html`<uui-button
label="edit"
look="secondary"
color=${this._contentInvalid ? 'danger' : ''}
href=${this._workspaceEditContentPath}>
<uui-icon name="icon-edit"></uui-icon>
${this._contentInvalid
? html`<uui-badge attention color="danger" label="Invalid content">!</uui-badge>`
: nothing}
</uui-button>`
: nothing}`;
}
#renderEditSettingsAction() {
return html`
${this._hasSettings && this._workspaceEditSettingsPath
? html`<uui-button
label="Edit settings"
look="secondary"
color=${this._settingsInvalid ? 'danger' : ''}
href=${this._workspaceEditSettingsPath}>
<uui-icon name="icon-settings"></uui-icon>
${this._settingsInvalid
? html`<uui-badge attention color="danger" label="Invalid settings">!</uui-badge>`
: nothing}
</uui-button>`
: nothing}
`;
}
#renderDeleteAction() {
if (this.readonly) return nothing;
return html` <uui-button label="delete" look="secondary" @click=${() => this.#context.requestDelete()}>
<uui-icon name="icon-remove"></uui-icon>
</uui-button>`;
}
override render() {
return this.#renderBlock();
}

View File

@@ -14,6 +14,7 @@ export const manifests: Array<ManifestTypes> = [
propertyEditorSchemaAlias: UMB_BLOCK_LIST_PROPERTY_EDITOR_ALIAS,
icon: 'icon-thumbnail-list',
group: 'lists',
supportsReadOnly: true,
settings: {
properties: [
{

View File

@@ -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)}
<umb-block-list-entry
.contentUdi=${layoutEntry.contentUdi}
.layout=${layoutEntry}
?readonly=${this.readonly}
${umbDestroyOnDisconnect()}>
</umb-block-list-entry>
`,
)}
<uui-button-group> ${this.#renderCreateButton()} ${this.#renderPasteButton()} </uui-button-group>`;
}
#renderInlineCreateButton(index: number) {
if (this.readonly) return nothing;
return html`<uui-button-inline-create
label=${this._createButtonLabel}
href=${this._catalogueRouteBuilder?.({ view: 'create', index: index }) ?? ''}></uui-button-inline-create>`;
}
#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`<uui-button-inline-create
label=${this._createButtonLabel}
href=${this._catalogueRouteBuilder?.({ view: 'create', index: index }) ?? ''}></uui-button-inline-create>
<umb-block-list-entry
.contentUdi=${layoutEntry.contentUdi}
.layout=${layoutEntry}
${umbDestroyOnDisconnect()}>
</umb-block-list-entry> `,
)}
<uui-button-group>
<uui-button look="placeholder" label=${this._createButtonLabel} href=${createPath ?? ''}></uui-button>
<uui-button
label=${this.localize.term('content_createFromClipboard')}
look="placeholder"
href=${this._catalogueRouteBuilder?.({ view: 'clipboard', index: -1 }) ?? ''}>
<uui-icon name="icon-paste-in"></uui-icon>
</uui-button>
</uui-button-group>`;
return html`
<uui-button look="placeholder" label=${this._createButtonLabel} href=${createPath ?? ''}></uui-button>
`;
}
#renderPasteButton() {
if (this.readonly) return nothing;
return html`
<uui-button
label=${this.localize.term('content_createFromClipboard')}
look="placeholder"
href=${this._catalogueRouteBuilder?.({ view: 'clipboard', index: -1 }) ?? ''}>
<uui-icon name="icon-paste-in"></uui-icon>
</uui-button>
`;
}
static override styles = [

View File

@@ -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);
}

View File

@@ -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<LayoutDataType extends UmbBlockLayoutBaseM
#variantId = new UmbClassState<UmbVariantId | undefined>(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<LayoutDataType extends UmbBlockLayoutBaseM
this.observe(context.variantId, (variantId) => {
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) => {