mega commit tuesday

This commit is contained in:
Niels Lyngsø
2024-02-06 17:16:13 +01:00
parent 8936f9fb1e
commit 722ebeff34
30 changed files with 536 additions and 260 deletions

View File

@@ -0,0 +1,125 @@
import { UmbBlockGridEntryContext } from '../../context/block-grid-entry.context.js';
import { html, css, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import type { UmbBlockGridLayoutModel } from '@umbraco-cms/backoffice/block';
import '../ref-list-block/index.js';
import '../inline-list-block/index.js';
/**
* @element umb-property-editor-ui-block-grid-block
*/
@customElement('umb-property-editor-ui-block-grid-block')
export class UmbPropertyEditorUIBlockGridBlockElement extends UmbLitElement implements UmbPropertyEditorUiElement {
//
@property({ attribute: false })
public get layout(): UmbBlockGridLayoutModel | undefined {
return this._layout;
}
public set layout(value: UmbBlockGridLayoutModel | undefined) {
this._layout = value;
this.#context.setLayout(value);
}
private _layout?: UmbBlockGridLayoutModel | undefined;
#context = new UmbBlockGridEntryContext(this);
@state()
_contentUdi?: string;
@state()
_hasSettings = false;
@state()
_label = '';
@state()
_workspaceEditPath?: string;
@state()
_inlineEditingMode?: boolean;
// TODO: Move type for the Block Properties, and use it on the Element Interface for the Manifest.
@state()
_blockViewProps: {
label?: string;
} = {};
constructor() {
super();
this.observe(this.#context.workspaceEditPath, (workspaceEditPath) => {
this._workspaceEditPath = workspaceEditPath;
});
this.observe(this.#context.contentUdi, (contentUdi) => {
this._contentUdi = contentUdi;
});
this.observe(this.#context.blockTypeSettingsElementTypeKey, (blockTypeSettingsElementTypeKey) => {
this._hasSettings = !!blockTypeSettingsElementTypeKey;
});
this.observe(this.#context.label, (label) => {
this._blockViewProps.label = label;
this._label = label;
});
}
#renderRefBlock() {
return html`<umb-ref-grid-block .label=${this._label}></umb-ref-grid-block>`;
}
#renderBlock() {
return html`
<umb-extension-slot
type="blockEditorCustomView"
default-element=${'umb-ref-grid-block'}
.props=${this._blockViewProps}
>${this.#renderRefBlock()}</umb-extension-slot
>
<uui-action-bar>
${this._workspaceEditPath
? html`<uui-button label="edit" compact href=${this._workspaceEditPath}>
<uui-icon name="icon-edit"></uui-icon>
</uui-button>`
: ''}
${this._workspaceEditPath && this._hasSettings
? html`<uui-button label="Edit settings" compact href=${this._workspaceEditPath + '/view/settings'}>
<uui-icon name="icon-settings"></uui-icon>
</uui-button>`
: ''}
<uui-button label="delete" compact @click=${this.#context.requestDelete}>
<uui-icon name="icon-remove"></uui-icon>
</uui-button>
</uui-action-bar>
`;
}
render() {
return this.layout && this._contentUdi ? this.#renderBlock() : '';
}
static styles = [
css`
:host {
position: relative;
display: block;
}
uui-action-bar {
position: absolute;
top: var(--uui-size-2);
right: var(--uui-size-2);
}
:host([drag-placeholder]) {
opacity: 0.2;
}
`,
];
}
export default UmbPropertyEditorUIBlockGridBlockElement;
declare global {
interface HTMLElementTagNameMap {
'umb-property-editor-ui-block-grid-block': UmbPropertyEditorUIBlockGridBlockElement;
}
}

View File

@@ -0,0 +1 @@
export * from './block-grid-block.element.js';

View File

@@ -0,0 +1,5 @@
import type { UmbBlockGridEntriesContext } from './block-grid-entries.context.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
// TODO: Make discriminator method for this:
export const UMB_BLOCK_GRID_ENTRIES_CONTEXT = new UmbContextToken<UmbBlockGridEntriesContext>('UmbBlockEntriesContext');

View File

@@ -0,0 +1,93 @@
import { UMB_BLOCK_CATALOGUE_MODAL } from '../../block/index.js';
import { UMB_BLOCK_GRID_MANAGER_CONTEXT } from '../manager/block-grid-manager.context.js';
import type { UmbBlockGridLayoutModel } from '../types.js';
import { UMB_BLOCK_GRID_ENTRIES_CONTEXT } from './block-grid-entries.context-token.js';
import { UMB_BLOCK_GRID_ENTRY_CONTEXT } from './block-grid-entry.context-token.js';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { type UmbModalRouteBuilder, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal';
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
export class UmbBlockGridEntriesContext extends UmbContextBase<UmbBlockGridEntriesContext> {
//
#blockManager?: typeof UMB_BLOCK_GRID_MANAGER_CONTEXT.TYPE;
#catalogueModal: UmbModalRouteRegistrationController<typeof UMB_BLOCK_CATALOGUE_MODAL.DATA, undefined>;
#catalogueRouteBuilder?: UmbModalRouteBuilder;
#layoutEntries = new UmbArrayState<UmbBlockGridLayoutModel>([], (x) => x.contentUdi);
layoutEntries = this.#layoutEntries.asObservable();
setLayoutEntries(layoutEntries: Array<UmbBlockGridLayoutModel>) {
this.#layoutEntries.setValue(layoutEntries);
}
getLayoutEntries() {
return this.#layoutEntries.value;
}
setParentKey(contentUdi: string) {
this.#catalogueModal.setUniquePathValue('parentUnique', contentUdi);
}
getParentKey() {
return '';
}
setAreaKey(areaKey: string) {
this.#catalogueModal.setUniquePathValue('areaKey', areaKey);
}
getAreaKey() {
return '';
}
constructor(host: UmbControllerHost) {
super(host, UMB_BLOCK_GRID_ENTRIES_CONTEXT.toString());
this.#catalogueModal = new UmbModalRouteRegistrationController(this, UMB_BLOCK_CATALOGUE_MODAL)
.addUniquePaths(['propertyAlias', 'parentUnique', 'areaKey'])
.addAdditionalPath(':view/:index')
.onSetup((routingInfo) => {
// 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: [],
blockGroups: [],
openClipboard: routingInfo.view === 'clipboard',
blockOriginData: { index: index },
},
};
})
.observeRouteBuilder((routeBuilder) => {
this.#catalogueRouteBuilder = routeBuilder;
});
// TODO: Observe Blocks of the layout entries of this component.
this.consumeContext(UMB_BLOCK_GRID_MANAGER_CONTEXT, (blockGridManager) => {
this.#blockManager = blockGridManager;
this.#gotBlockManager();
});
}
#gotBlockManager() {
if (!this.#blockManager) return;
this.observe(
this.#blockManager.propertyAlias,
(alias) => {
this.#catalogueModal.setUniquePathValue('propertyAlias', alias);
},
'observePropertyAlias',
);
}
getPathForCreateBlock(index: number) {
return this.#catalogueRouteBuilder?.({ view: 'create', index: index });
}
getPathForClipboard(index: number) {
return this.#catalogueRouteBuilder?.({ view: 'clipboard', index: index });
}
// create Block?
deleteBlock() {}
}

View File

@@ -0,0 +1,5 @@
import type { UmbBlockGridEntryContext } from './block-grid-entry.context.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
// TODO: Make discriminator method for this:
export const UMB_BLOCK_GRID_ENTRY_CONTEXT = new UmbContextToken<UmbBlockGridEntryContext>('UmbBlockEntryContext');

View File

@@ -3,20 +3,25 @@ import {
UmbBlockContext,
type UmbBlockGridTypeModel,
type UmbBlockGridLayoutModel,
type UmbBlockGridLayoutAreaItemModel,
} from '@umbraco-cms/backoffice/block';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api';
export class UmbBlockGridContext extends UmbBlockContext<
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
export class UmbBlockGridEntryContext extends UmbBlockContext<
typeof UMB_BLOCK_GRID_MANAGER_CONTEXT,
typeof UMB_BLOCK_GRID_MANAGER_CONTEXT.TYPE,
UmbBlockGridTypeModel,
UmbBlockGridLayoutModel
> {
#inlineEditingMode = new UmbBooleanState(undefined);
inlineEditingMode = this.#inlineEditingMode.asObservable();
#areas = new UmbArrayState<UmbBlockGridLayoutAreaItemModel>([], (x) => x.key);
areas = this.#areas.asObservable();
constructor(host: UmbControllerHost) {
super(host, UMB_BLOCK_GRID_MANAGER_CONTEXT);
this.observe(this.layout, (layout) => {
this.#areas.setValue(layout?.areas ?? []);
});
}
_gotManager() {

View File

@@ -1,5 +0,0 @@
import type { UmbBlockGridContext } from './block-grid.context.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
// TODO: Make discriminator method for this:
export const UMB_BLOCK_GRID_CONTEXT = new UmbContextToken<UmbBlockGridContext>('UmbBlockContext');

View File

@@ -0,0 +1,2 @@
export * from './block-grid-entries.context-token.js';
export * from './block-grid-entry.context-token.js';

View File

@@ -1,2 +1,3 @@
export * from './workspace/index.js';
export * from './context/index.js';
export * from './types.js';
export * from './workspace/index.js';

View File

@@ -1,7 +1,9 @@
import type { UmbBlockGridLayoutModel, UmbBlockGridTypeModel } from '../types.js';
import { UmbBlockManagerContext } from '../../block/manager/block-manager.context.js';
import type { UmbBlockGridWorkspaceData } from '../index.js';
import type { UmbBlockTypeGroup } from '../../block-type/types.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
/**
* A implementation of the Block Manager specifically for the Block Grid Editor.
@@ -10,14 +12,16 @@ export class UmbBlockGridManagerContext<
BlockLayoutType extends UmbBlockGridLayoutModel = UmbBlockGridLayoutModel,
> extends UmbBlockManagerContext<UmbBlockGridTypeModel, BlockLayoutType> {
//
/*
#inlineEditingMode = new UmbBooleanState(undefined);
inlineEditingMode = this.#inlineEditingMode.asObservable();
//
#blockGroups = new UmbArrayState(<Array<UmbBlockTypeGroup>>[], (x) => x.key);
public readonly blockGroups = this.#blockGroups.asObservable();
setInlineEditingMode(inlineEditingMode: boolean | undefined) {
this.#inlineEditingMode.setValue(inlineEditingMode ?? false);
setBlockGroups(blockGroups: Array<UmbBlockTypeGroup>) {
this.#blockGroups.setValue(blockGroups);
}
getBlockGroups() {
return this.#blockGroups.value;
}
*/
_createBlock(modalData: UmbBlockGridWorkspaceData, layoutEntry: BlockLayoutType, contentElementTypeKey: string) {
// Here is room to append some extra layout properties if needed for this type.

View File

@@ -0,0 +1,109 @@
import { UmbBlockGridEntriesContext } from '../../context/block-grid-entries.context.js';
import type { UmbBlockGridLayoutModel } from '@umbraco-cms/backoffice/block';
import { html, customElement, state, repeat, css, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
/**
* @element umb-property-editor-ui-block-grid-entries
*/
@customElement('umb-property-editor-ui-block-grid-entries')
export class UmbPropertyEditorUIBlockGridEntriesElement extends UmbLitElement {
//
// TODO: Make sure Sorter callbacks handles columnSpan when retrieving a new entry.
#context = new UmbBlockGridEntriesContext(this);
@property({ attribute: false })
public set layoutEntries(value: Array<UmbBlockGridLayoutModel>) {
this.#context.setLayoutEntries(value);
}
public get layoutEntries(): Array<UmbBlockGridLayoutModel> {
return this.#context.getLayoutEntries();
}
@property({ attribute: false })
public set parentUnique(value: string) {
this.#context.setParentKey(value);
}
public get parentUnique(): string {
return this.#context.getParentKey();
}
@property({ attribute: false })
public set areaKey(value: string) {
this.#context.setAreaKey(value);
}
public get areaKey(): string {
return this.#context.getAreaKey();
}
@state()
private _layoutEntries: Array<UmbBlockGridLayoutModel> = [];
@state()
private _createButtonLabel = this.localize.term('blockEditor_addBlock');
constructor() {
super();
this.observe(this.#context.layoutEntries, (layoutEntries) => {
this._layoutEntries = layoutEntries;
});
}
render() {
// TODO: Missing ability to jump directly to creating a Block, when there is only one Block Type.
return html` ${repeat(
this._layoutEntries,
(x) => x.contentUdi,
(layoutEntry, index) =>
html`<uui-button-inline-create
href=${this.#context.getPathForCreateBlock(index) ?? ''}></uui-button-inline-create>
<umb-property-editor-ui-block-list-block data-udi=${layoutEntry.contentUdi} .layout=${layoutEntry}>
</umb-property-editor-ui-block-list-block> `,
)}
<uui-button-group>
<uui-button
id="add-button"
look="placeholder"
label=${this._createButtonLabel}
href=${this.#context.getPathForCreateBlock(-1) ?? ''}></uui-button>
<uui-button
label=${this.localize.term('content_createFromClipboard')}
look="placeholder"
href=${this.#context.getPathForClipboard(-1) ?? ''}>
<uui-icon name="icon-paste-in"></uui-icon>
</uui-button>
</uui-button-group>`;
//
}
static styles = [
UmbTextStyles,
css`
:host {
display: grid;
gap: 1px;
}
> div {
display: flex;
flex-direction: column;
align-items: stretch;
}
uui-button-group {
padding-top: 1px;
display: grid;
grid-template-columns: 1fr auto;
}
`,
];
}
export default UmbPropertyEditorUIBlockGridEntriesElement;
declare global {
interface HTMLElementTagNameMap {
'umb-property-editor-ui-block-grid-entries': UmbPropertyEditorUIBlockGridEntriesElement;
}
}

View File

@@ -1,137 +0,0 @@
import {
UMB_BLOCK_CATALOGUE_MODAL,
type UmbBlockGridLayoutModel,
type UmbBlockTypeBaseModel,
} from '@umbraco-cms/backoffice/block';
import { html, customElement, state, repeat, css, property } from '@umbraco-cms/backoffice/external/lit';
import { type UmbModalRouteBuilder, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal';
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
/**
* @element umb-property-editor-ui-block-grid-entries
*/
@customElement('umb-property-editor-ui-block-grid-entries')
export class UmbPropertyEditorUIBlockGridEntriesElement extends UmbLitElement {
// TODO: Make sure Sorter handles columnSpan when retrieving a new entry.
#catalogueModal: UmbModalRouteRegistrationController<typeof UMB_BLOCK_CATALOGUE_MODAL.DATA, undefined>;
@state()
private _catalogueRouteBuilder?: UmbModalRouteBuilder;
@state()
private _blocks?: Array<UmbBlockTypeBaseModel>;
@property({ attribute: false })
public set layoutEntries(value: Array<UmbBlockGridLayoutModel>) {
this._layoutEntries = value;
}
public get layoutEntries(): Array<UmbBlockGridLayoutModel> {
return this._layoutEntries;
}
private _layoutEntries: Array<UmbBlockGridLayoutModel> = [];
@state()
private _createButtonLabel = this.localize.term('content_createEmpty');
constructor() {
super();
// TODO: Observe Blocks of the layout entries of this component.
// TODO: Could this become part of the Block Manager Context?
this.consumeContext(UMB_PROPERTY_CONTEXT, (propertyContext) => {
this.observe(
propertyContext?.alias,
(alias) => {
this.#catalogueModal.setUniquePathValue('propertyAlias', alias);
},
'observePropertyAlias',
);
});
// Maybe this can be moved to the Block Manager Context? As I don't want to know about groups here. Maybe just part of the onSetup method?
this.#catalogueModal = new UmbModalRouteRegistrationController(this, UMB_BLOCK_CATALOGUE_MODAL)
.addUniquePaths(['propertyAlias', 'parentUnique'])
.addAdditionalPath(':view/:index')
.onSetup((routingInfo) => {
const index = routingInfo.index ? parseInt(routingInfo.index) : -1;
return {
data: {
blocks: this._blocks ?? [],
//blockGroups: this._blockGroups ?? [],
blockGroups: [],
openClipboard: routingInfo.view === 'clipboard',
blockOriginData: { index: index },
},
};
})
.observeRouteBuilder((routeBuilder) => {
this._catalogueRouteBuilder = routeBuilder;
});
}
render() {
let createPath: string | undefined;
if (this._blocks?.length === 1) {
const elementKey = this._blocks[0].contentElementTypeKey;
createPath =
this._catalogueRouteBuilder?.({ view: 'create', index: -1 }) + 'modal/umb-modal-workspace/create/' + elementKey;
} else {
createPath = this._catalogueRouteBuilder?.({ view: 'create', index: -1 });
}
return html` ${repeat(
this._layoutEntries,
(x) => x.contentUdi,
(layoutEntry, index) =>
html`<uui-button-inline-create
href=${this._catalogueRouteBuilder?.({ view: 'create', index: index }) ?? ''}></uui-button-inline-create>
<umb-property-editor-ui-block-list-block data-udi=${layoutEntry.contentUdi} .layout=${layoutEntry}>
</umb-property-editor-ui-block-list-block> `,
)}
<uui-button-group>
<uui-button
id="add-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>`;
//
}
static styles = [
UmbTextStyles,
css`
:host {
display: grid;
gap: 1px;
}
> div {
display: flex;
flex-direction: column;
align-items: stretch;
}
uui-button-group {
padding-top: 1px;
display: grid;
grid-template-columns: 1fr auto;
}
`,
];
}
export default UmbPropertyEditorUIBlockGridEntriesElement;
declare global {
interface HTMLElementTagNameMap {
'umb-property-editor-ui-block-grid-entries': UmbPropertyEditorUIBlockGridEntriesElement;
}
}

View File

@@ -1,40 +1,31 @@
import { UmbBlockGridManagerContext } from '../../manager/block-grid-manager.context.js';
import { UMB_BLOCK_GRID_PROPERTY_EDITOR_ALIAS } from './manifests.js';
import { html, customElement, property, state, css } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
import type { UmbBlockGridLayoutModel, UmbBlockTypeBaseModel, UmbBlockTypeGroup } from '@umbraco-cms/backoffice/block';
import type {
UmbBlockGridLayoutModel,
UmbBlockGridTypeModel,
UmbBlockGridValueModel,
UmbBlockTypeGroup,
} from '@umbraco-cms/backoffice/block';
import type { NumberRangeValueType } from '@umbraco-cms/backoffice/models';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { UMB_BLOCK_GRID_PROPERTY_EDITOR_ALIAS } from './manifests';
/**
* @element umb-property-editor-ui-block-grid
*/
@customElement('umb-property-editor-ui-block-grid')
export class UmbPropertyEditorUIBlockGridElement extends UmbLitElement implements UmbPropertyEditorUiElement {
@property()
value = '';
@state()
private _limitMin?: number;
@state()
private _limitMax?: number;
@state()
private _blocks?: Array<UmbBlockTypeBaseModel>;
@state()
private _blockGroups?: Array<UmbBlockTypeGroup>;
@state()
private _rootLayouts: Array<UmbBlockGridLayoutModel> = [];
@state()
private _directRoute?: string;
@state()
private _createButtonLabel = this.localize.term('blockEditor_addBlock');
#context = new UmbBlockGridManagerContext(this);
//
private _value: UmbBlockGridValueModel = {
layout: {},
contentData: [],
settingsData: [],
};
@property({ attribute: false })
public set config(config: UmbPropertyEditorConfigCollection | undefined) {
@@ -45,28 +36,42 @@ export class UmbPropertyEditorUIBlockGridElement extends UmbLitElement implement
this._limitMin = validationLimit?.min;
this._limitMax = validationLimit?.max;
this._blocks = config.getValueByAlias<Array<UmbBlockTypeBaseModel>>('blocks') ?? [];
this._blockGroups = config.getValueByAlias<Array<UmbBlockTypeGroup>>('blockGroups') ?? [];
const blocks = config.getValueByAlias<Array<UmbBlockGridTypeModel>>('blocks') ?? [];
this.#context.setBlockTypes(blocks);
const customCreateButtonLabel = config.getValueByAlias<string>('createLabel');
if (customCreateButtonLabel) {
this._createButtonLabel = customCreateButtonLabel;
} else if (this._blocks.length === 1) {
this._createButtonLabel = this.localize.term('blockEditor_addThis', [this._blocks[0].label]);
}
const blockGroups = config.getValueByAlias<Array<UmbBlockTypeGroup>>('blockGroups') ?? [];
this.#context.setBlockGroups(blockGroups);
//const useInlineEditingAsDefault = config.getValueByAlias<boolean>('useInlineEditingAsDefault');
//this.#context.setInlineEditingMode(useInlineEditingAsDefault);
//config.useSingleBlockMode
//config.useLiveEditing
//config.useInlineEditingAsDefault
this.style.maxWidth = config.getValueByAlias<string>('maxPropertyWidth') ?? '';
//this.#context.setEditorConfiguration(config);
//config.useLiveEditing, is covered by the EditorConfiguration of context.
this.#context.setEditorConfiguration(config);
}
#context = new UmbBlockGridManagerContext(this);
//
@state()
private _limitMin?: number;
@state()
private _limitMax?: number;
@property({ attribute: false })
public get value(): UmbBlockGridValueModel {
return this._value;
}
public set value(value: UmbBlockGridValueModel | undefined) {
const buildUpValue: Partial<UmbBlockGridValueModel> = value ? { ...value } : {};
buildUpValue.layout ??= {};
buildUpValue.contentData ??= [];
buildUpValue.settingsData ??= [];
this._value = buildUpValue as UmbBlockGridValueModel;
this.#context.setLayouts(this._value.layout[UMB_BLOCK_GRID_PROPERTY_EDITOR_ALIAS] ?? []);
this.#context.setContents(buildUpValue.contentData);
this.#context.setSettings(buildUpValue.settingsData);
}
@state()
private _rootLayouts: Array<UmbBlockGridLayoutModel> = [];
constructor() {
super();
@@ -92,14 +97,13 @@ export class UmbPropertyEditorUIBlockGridElement extends UmbLitElement implement
//console.log('settings changed', this._value);
this.dispatchEvent(new UmbChangeEvent());
});
this.observe(this.#context.blockTypes, (blockTypes) => {
this._blocks = blockTypes;
});
}
render() {
return html`<umb-property-editor-ui-block-grid-entries
.layoutEntries=${this._rootLayouts}></umb-property-editor-ui-block-grid-entries>`;
.layoutEntries=${this._rootLayouts}
.parentUnique=${'root'}
.areaKey=${'root'}></umb-property-editor-ui-block-grid-entries>`;
}
static styles = [

View File

@@ -1,8 +1,9 @@
import type { UmbBlockTypeBaseModel, UmbBlockTypeWithGroupKey } from '../block-type/index.js';
import type { UmbBlockLayoutBaseModel } from '../index.js';
import type { UmbBlockLayoutBaseModel, UmbBlockValueType } from '../index.js';
export const UMB_BLOCK_GRID_TYPE = 'block-grid-type';
// Configuration models:
export interface UmbBlockGridTypeModel extends UmbBlockTypeBaseModel {
columnSpanOptions: Array<number>;
allowAtRoot: boolean;
@@ -23,6 +24,10 @@ export interface UmbBlockGridGroupTypeConfiguration extends Partial<UmbBlockGrid
blocks: Array<UmbBlockTypeWithGroupKey>;
}
// Content models:
export interface UmbBlockGridValueModel extends UmbBlockValueType<UmbBlockGridLayoutModel> {}
export interface UmbBlockGridLayoutModel extends UmbBlockLayoutBaseModel {
columnSpan: number;
rowSpan: number;

View File

@@ -1,4 +1,4 @@
import { UmbBlockListContext } from '../../context/block-list.context.js';
import { UmbBlockListEntryContext } from '../../context/block-list-entry.context.js';
import { html, css, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
@@ -23,7 +23,7 @@ export class UmbPropertyEditorUIBlockListBlockElement extends UmbLitElement impl
}
private _layout?: UmbBlockLayoutBaseModel | undefined;
#context = new UmbBlockListContext(this);
#context = new UmbBlockListEntryContext(this);
@state()
_contentUdi?: string;
@@ -67,21 +67,6 @@ export class UmbPropertyEditorUIBlockListBlockElement extends UmbLitElement impl
});
}
#requestDelete() {
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, async (modalManager) => {
const modalContext = modalManager.open(UMB_CONFIRM_MODAL, {
data: {
headline: `Delete ${this._label}`,
content: 'Are you sure you want to delete this [INSERT BLOCK TYPE NAME]?',
confirmLabel: 'Delete',
color: 'danger',
},
});
await modalContext.onSubmit();
this.#context.delete();
});
}
#renderRefBlock() {
return html`<umb-ref-list-block .label=${this._label}></umb-ref-list-block>`;
}
@@ -109,7 +94,7 @@ export class UmbPropertyEditorUIBlockListBlockElement extends UmbLitElement impl
<uui-icon name="icon-settings"></uui-icon>
</uui-button>`
: ''}
<uui-button label="delete" compact @click=${this.#requestDelete}>
<uui-button label="delete" compact @click=${this.#context.requestDelete}>
<uui-icon name="icon-remove"></uui-icon>
</uui-button>
</uui-action-bar>

View File

@@ -1,4 +1,4 @@
import { UMB_BLOCK_LIST_CONTEXT } from '../../index.js';
import { UMB_BLOCK_LIST_ENTRY_CONTEXT } from '../../index.js';
import type { UMB_BLOCK_WORKSPACE_CONTEXT } from '../../../block/index.js';
import { UMB_BLOCK_WORKSPACE_ALIAS } from '../../../block/index.js';
import { UmbExtensionsApiInitializer, createExtensionApi } from '@umbraco-cms/backoffice/extension-api';
@@ -13,7 +13,7 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
*/
@customElement('umb-inline-list-block')
export class UmbInlineListBlockElement extends UmbLitElement {
#blockContext?: typeof UMB_BLOCK_LIST_CONTEXT.TYPE;
#blockContext?: typeof UMB_BLOCK_LIST_ENTRY_CONTEXT.TYPE;
#workspaceContext?: typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE;
#contentUdi?: string;
@@ -26,7 +26,7 @@ export class UmbInlineListBlockElement extends UmbLitElement {
constructor() {
super();
this.consumeContext(UMB_BLOCK_LIST_CONTEXT, (blockContext) => {
this.consumeContext(UMB_BLOCK_LIST_ENTRY_CONTEXT, (blockContext) => {
this.#blockContext = blockContext;
this.observe(
this.#blockContext.contentUdi,

View File

@@ -1,4 +1,4 @@
import { UMB_BLOCK_LIST_CONTEXT } from '../../context/block-list.context-token.js';
import { UMB_BLOCK_LIST_ENTRY_CONTEXT } from '../../context/block-list-entry.context-token.js';
import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
@@ -17,7 +17,7 @@ export class UmbRefListBlockElement extends UmbLitElement {
constructor() {
super();
this.consumeContext(UMB_BLOCK_LIST_CONTEXT, (context) => {
this.consumeContext(UMB_BLOCK_LIST_ENTRY_CONTEXT, (context) => {
this.observe(
context.workspaceEditPath,
(workspaceEditPath) => {

View File

@@ -0,0 +1,5 @@
import type { UmbBlockListEntryContext } from './block-list-entry.context.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
// TODO: Make discriminator method for this:
export const UMB_BLOCK_LIST_ENTRY_CONTEXT = new UmbContextToken<UmbBlockListEntryContext>('UmbBlockEntryContext');

View File

@@ -2,7 +2,7 @@ import { UMB_BLOCK_LIST_MANAGER_CONTEXT } from '../manager/block-list-manager.co
import { UmbBlockContext } from '@umbraco-cms/backoffice/block';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api';
export class UmbBlockListContext extends UmbBlockContext<
export class UmbBlockListEntryContext extends UmbBlockContext<
typeof UMB_BLOCK_LIST_MANAGER_CONTEXT,
typeof UMB_BLOCK_LIST_MANAGER_CONTEXT.TYPE
> {

View File

@@ -1,5 +0,0 @@
import type { UmbBlockListContext } from './block-list.context.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
// TODO: Make discriminator method for this:
export const UMB_BLOCK_LIST_CONTEXT = new UmbContextToken<UmbBlockListContext>('UmbBlockContext');

View File

@@ -0,0 +1 @@
export * from './block-list-entry.context-token.js';

View File

@@ -1,2 +1,3 @@
export * from './context/index.js';
export * from './types.js';
export * from './workspace/index.js';
export * from './context/block-list.context-token.js';

View File

@@ -1,6 +1,7 @@
import { UmbBlockListManagerContext } from '../../manager/block-list-manager.context.js';
import '../../components/block-list-block/index.js';
import type { UmbPropertyEditorUIBlockListBlockElement } from '../../components/block-list-block/index.js';
import type { UmbBlockListLayoutModel, UmbBlockListValueModel } from '../../types.js';
import { UMB_BLOCK_LIST_PROPERTY_EDITOR_ALIAS } from './manifests.js';
import { html, customElement, property, state, repeat, css } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
@@ -8,7 +9,7 @@ import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extensi
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
import { UMB_BLOCK_CATALOGUE_MODAL } from '@umbraco-cms/backoffice/block';
import type { UmbBlockLayoutBaseModel, UmbBlockTypeBaseModel, UmbBlockValueType } from '@umbraco-cms/backoffice/block';
import type { UmbBlockLayoutBaseModel, UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block';
import type { NumberRangeValueType } from '@umbraco-cms/backoffice/models';
import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/modal';
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal';
@@ -17,10 +18,6 @@ import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter';
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
export interface UmbBlockListLayoutModel extends UmbBlockLayoutBaseModel {}
export interface UmbBlockListValueModel extends UmbBlockValueType<UmbBlockListLayoutModel> {}
const SORTER_CONFIG: UmbSorterConfig<UmbBlockListLayoutModel, UmbPropertyEditorUIBlockListBlockElement> = {
getUniqueOfElement: (element) => {
return element.getAttribute('data-udi');
@@ -94,9 +91,8 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement
const useInlineEditingAsDefault = config.getValueByAlias<boolean>('useInlineEditingAsDefault');
this.#context.setInlineEditingMode(useInlineEditingAsDefault);
//config.useSingleBlockMode
//config.useLiveEditing
//config.useInlineEditingAsDefault
// TODO:
//config.useSingleBlockMode, not done jey
this.style.maxWidth = config.getValueByAlias<string>('maxPropertyWidth') ?? '';
this.#context.setEditorConfiguration(config);

View File

@@ -1,7 +1,9 @@
import type { UmbBlockTypeBaseModel } from '../block-type/index.js';
import type { UmbBlockLayoutBaseModel } from '../index.js';
import type { UmbBlockLayoutBaseModel, UmbBlockValueType } from '../index.js';
export const UMB_BLOCK_LIST_TYPE = 'block-list-type';
export interface UmbBlockListTypeModel extends UmbBlockTypeBaseModel {}
export interface UmbBlockListLayoutModel extends UmbBlockLayoutBaseModel {}
export interface UmbBlockListValueModel extends UmbBlockValueType<UmbBlockListLayoutModel> {}

View File

@@ -0,0 +1,34 @@
import type { UmbBlockTypeBaseModel } from '../../block-type/types.js';
import type { UmbBlockLayoutBaseModel } from '../types.js';
import type { UMB_BLOCK_MANAGER_CONTEXT, UmbBlockManagerContext } from '../manager/index.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export abstract class UmbBlockContext<
BlockManagerContextTokenType extends UmbContextToken<BlockManagerContextType, BlockManagerContextType>,
BlockManagerContextType extends UmbBlockManagerContext<BlockType, BlockLayoutType>,
BlockType extends UmbBlockTypeBaseModel = UmbBlockTypeBaseModel,
BlockLayoutType extends UmbBlockLayoutBaseModel = UmbBlockLayoutBaseModel,
> extends UmbContextBase<
UmbBlockContext<BlockManagerContextTokenType, BlockManagerContextType, BlockType, BlockLayoutType>
> {
//
_manager?: BlockManagerContextType;
constructor(host: UmbControllerHost, blockManagerContextToken: BlockManagerContextTokenType) {
super(host, UMB_BLOCK_ENTITY_CONTEXT.toString());
}
// Public methods:
//edit?
//editSettings
//requestDelete
//delete
//copy
}
export const UMB_BLOCK_ENTITY_CONTEXT = new UmbContextToken<
UmbBlockContext<typeof UMB_BLOCK_MANAGER_CONTEXT, typeof UMB_BLOCK_MANAGER_CONTEXT.TYPE>
>('UmbBlockEntryContext');

View File

@@ -6,6 +6,7 @@ import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbObjectState, UmbStringState } from '@umbraco-cms/backoffice/observable-api';
import { encodeFilePath } from '@umbraco-cms/backoffice/utils';
import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
export abstract class UmbBlockContext<
BlockManagerContextTokenType extends UmbContextToken<BlockManagerContextType, BlockManagerContextType>,
@@ -21,6 +22,8 @@ export abstract class UmbBlockContext<
#blockTypeName = new UmbStringState(undefined);
public readonly blockTypeName = this.#blockTypeName.asObservable();
// TODO: index state + observable?
#label = new UmbStringState('');
public readonly label = this.#label.asObservable();
@@ -59,6 +62,15 @@ export abstract class UmbBlockContext<
this.#layout.setValue(layout);
}
/**
* Get the current value of this Blocks label.
* @method getLabel
* @returns {string}
*/
getLabel() {
return this.#label.value;
}
constructor(host: UmbControllerHost, blockManagerContextToken: BlockManagerContextTokenType) {
super(host, UMB_BLOCK_ENTITY_CONTEXT.toString());
@@ -190,14 +202,34 @@ export abstract class UmbBlockContext<
// Public methods:
//activate
public edit() {}
//editSettings
requestDelete() {
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, async (modalManager) => {
const modalContext = modalManager.open(UMB_CONFIRM_MODAL, {
data: {
headline: `Delete ${this.getLabel()}`,
content: 'Are you sure you want to delete this [INSERT BLOCK TYPE NAME]?',
confirmLabel: 'Delete',
color: 'danger',
},
});
await modalContext.onSubmit();
this.delete();
});
}
public delete() {
if (!this._manager) return;
const contentUdi = this.#layout.value?.contentUdi;
if (!contentUdi) return;
this._manager.deleteBlock(contentUdi);
}
//copy
}
export const UMB_BLOCK_ENTITY_CONTEXT = new UmbContextToken<
UmbBlockContext<typeof UMB_BLOCK_MANAGER_CONTEXT, typeof UMB_BLOCK_MANAGER_CONTEXT.TYPE>
>('UmbBlockContext');
>('UmbBlockEntryContext');

View File

@@ -10,6 +10,7 @@ import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/mod
import type { UmbContentTypeModel } from '@umbraco-cms/backoffice/content-type';
import { UmbId } from '@umbraco-cms/backoffice/id';
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
// TODO: We are using backend model here, I think we should get our own model:
type ElementTypeModel = UmbContentTypeModel;
@@ -20,10 +21,14 @@ export abstract class UmbBlockManagerContext<
> extends UmbContextBase<UmbBlockManagerContext> {
//
#contentTypeRepository = new UmbDocumentTypeDetailRepository(this);
#workspaceModal: UmbModalRouteRegistrationController;
#workspacePath = new UmbStringState(undefined);
workspacePath = this.#workspacePath.asObservable();
#propertyAlias = new UmbStringState(undefined);
propertyAlias = this.#propertyAlias.asObservable();
#contentTypes = new UmbArrayState(<Array<ElementTypeModel>>[], (x) => x.unique);
public readonly contentTypes = this.#contentTypes.asObservable();
@@ -42,6 +47,14 @@ export abstract class UmbBlockManagerContext<
#settings = new UmbArrayState(<Array<UmbBlockDataType>>[], (x) => x.udi);
public readonly settings = this.#settings.asObservable();
setPropertyAlias(alias: string) {
this.#propertyAlias.setValue(alias);
this.#workspaceModal.setUniquePathValue('propertyAlias', alias);
}
getPropertyAlias() {
this.#propertyAlias.value;
}
setEditorConfiguration(configs: UmbPropertyEditorConfigCollection) {
this.#editorConfiguration.setValue(configs);
}
@@ -67,9 +80,18 @@ export abstract class UmbBlockManagerContext<
constructor(host: UmbControllerHost) {
super(host, UMB_BLOCK_MANAGER_CONTEXT);
// TODO: This might will need the property alias as part of the URL, to avoid collision if multiple of these Editor on same Node.
// IDEA: Make a Workspace registration controller that can be used to register a workspace, which does both edit and create?.
new UmbModalRouteRegistrationController(this, UMB_BLOCK_WORKSPACE_MODAL)
this.consumeContext(UMB_PROPERTY_CONTEXT, (propertyContext) => {
this.observe(
propertyContext?.alias,
(alias) => {
this.#propertyAlias.setValue(alias);
},
'observePropertyAlias',
);
});
this.#workspaceModal = new UmbModalRouteRegistrationController(this, UMB_BLOCK_WORKSPACE_MODAL)
.addUniquePaths(['propertyAlias'])
.addAdditionalPath('block')
.onSetup(() => {
return { data: { entityType: 'block', preset: {} }, modal: { size: 'medium' } };
@@ -191,9 +213,12 @@ export abstract class UmbBlockManagerContext<
return true;
}
// Idea: should we return true if it was successful?
deleteBlock(contentUdi: string) {
const layout = this._layouts.value.find((x) => x.contentUdi === contentUdi);
if (!layout) return;
if (!layout) {
throw new Error(`Cannot delete block, missing layout for ${contentUdi}`);
}
if (layout.settingsUdi) {
this.#settings.removeOne(layout.settingsUdi);

View File

@@ -76,6 +76,7 @@ export class UmbBlockWorkspaceContext<
}
this.observe(
// TODO: Make a general concept of Block Entries Context, use it to retrieve the layout:
this.#blockManager.layoutOf(unique),
(layoutData) => {
this.#layout.setValue(layoutData as LayoutDataType);

View File

@@ -1,18 +0,0 @@
import { expect, fixture, html } from '@open-wc/testing';
import { UmbSorterConfig, UmbSorterController } from './sorter.controller.js';
import type UmbTestSorterControllerElement from './stories/test-sorter-controller.element.js';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import { customElement } from '@umbraco-cms/backoffice/external/lit';
describe('UmbContextConsumer', () => {
let hostElement: UmbTestSorterControllerElement;
beforeEach(async () => {
hostElement = await fixture(html` <test-my-sorter-controller></test-my-sorter-controller> `);
});
// TODO: Testing ideas:
// - Test that the model is updated correctly?
// - Test that the DOM is updated correctly?
// - Use the controller to sort the DOM and test that the model is updated correctly...
});