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:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user