Merge pull request #1266 from umbraco/feature/block-grid-editor-take-3
Block Grid Editor Block Type Permissions
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
"ctrls",
|
||||
"devs",
|
||||
"Elementable",
|
||||
"iframes",
|
||||
"invariantable",
|
||||
"lucide",
|
||||
"Niels",
|
||||
|
||||
@@ -113,18 +113,20 @@ export const UmbControllerHostMixin = <T extends ClassConstructor>(superClass: T
|
||||
|
||||
destroy() {
|
||||
let ctrl: UmbController | undefined;
|
||||
//let prev = null;
|
||||
let prev = null;
|
||||
// Note: A very important way of doing this loop, as foreach will skip over the next item if the current item is removed.
|
||||
while ((ctrl = this.#controllers[0])) {
|
||||
ctrl.destroy();
|
||||
/*
|
||||
//This code can help debug if there is some controller that does not destroy properly: (When a controller is destroyed it should remove it self)
|
||||
|
||||
// Help developer realize that they made a mistake in code:
|
||||
if (ctrl === prev) {
|
||||
console.log('WUPS, we have a controller that does not destroy it self');
|
||||
debugger;
|
||||
throw new Error(
|
||||
`Controller with controller alias: '${ctrl.controllerAlias?.toString()}' and class name: '${
|
||||
(ctrl as any).constructor.name
|
||||
}', does not remove it self when destroyed. This can cause memory leaks. Please fix this issue.`,
|
||||
);
|
||||
}
|
||||
prev = ctrl;
|
||||
*/
|
||||
}
|
||||
this.#controllers.length = 0;
|
||||
}
|
||||
|
||||
@@ -674,6 +674,8 @@ export const data: Array<UmbMockDataTypeModel> = [
|
||||
{
|
||||
label: 'Mocked Block Type for Block Grid',
|
||||
contentElementTypeKey: '4f68ba66-6fb2-4778-83b8-6ab4ca3a7c5c',
|
||||
allowAtRoot: true,
|
||||
allowInAreas: true,
|
||||
rowMinSpan: 1,
|
||||
rowMaxSpan: 2,
|
||||
columnSpanOptions: [
|
||||
@@ -720,7 +722,7 @@ export const data: Array<UmbMockDataTypeModel> = [
|
||||
iconColor: '#FFFDD0',
|
||||
backgroundColor: '#633f32',
|
||||
editorSize: 'medium',
|
||||
icon: 'icon-coffee',
|
||||
allowInAreas: true,
|
||||
},
|
||||
|
||||
{
|
||||
@@ -728,37 +730,36 @@ export const data: Array<UmbMockDataTypeModel> = [
|
||||
contentElementTypeKey: 'headline-umbraco-demo-block-id',
|
||||
backgroundColor: 'gold',
|
||||
editorSize: 'medium',
|
||||
icon: 'icon-edit',
|
||||
groupKey: 'demo-block-group-id',
|
||||
allowInAreas: true,
|
||||
},
|
||||
{
|
||||
label: 'Image',
|
||||
contentElementTypeKey: 'image-umbraco-demo-block-id',
|
||||
editorSize: 'medium',
|
||||
icon: 'icon-picture',
|
||||
|
||||
groupKey: 'demo-block-group-id',
|
||||
allowInAreas: true,
|
||||
},
|
||||
{
|
||||
label: 'Rich Text',
|
||||
contentElementTypeKey: 'rich-text-umbraco-demo-block-id',
|
||||
editorSize: 'medium',
|
||||
icon: 'icon-diploma',
|
||||
groupKey: 'demo-block-group-id',
|
||||
allowInAreas: true,
|
||||
},
|
||||
{
|
||||
label: 'Two Column Layout',
|
||||
contentElementTypeKey: 'two-column-layout-umbraco-demo-block-id',
|
||||
editorSize: 'medium',
|
||||
icon: 'icon-book-alt',
|
||||
groupKey: 'demo-block-group-id',
|
||||
allowAtRoot: false,
|
||||
},
|
||||
{
|
||||
label: 'Test broken group key',
|
||||
contentElementTypeKey: 'test-block-id',
|
||||
editorSize: 'medium',
|
||||
icon: 'icon-war',
|
||||
groupKey: 'group-id-that-does-not-exist',
|
||||
allowAtRoot: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -143,6 +143,15 @@ export class UmbBlockGridEntriesElement extends UmbLitElement {
|
||||
onChange: ({ model }) => {
|
||||
this.#context.setLayouts(model);
|
||||
},
|
||||
onRequestMove: ({ item }) => {
|
||||
return this.#context.allowDrop(item.contentUdi);
|
||||
},
|
||||
onDisallowed: () => {
|
||||
this.setAttribute('disallow-drop', '');
|
||||
},
|
||||
onAllowed: () => {
|
||||
this.removeAttribute('disallow-drop');
|
||||
},
|
||||
});
|
||||
|
||||
#context = new UmbBlockGridEntriesContext(this);
|
||||
@@ -241,9 +250,29 @@ export class UmbBlockGridEntriesElement extends UmbLitElement {
|
||||
UmbTextStyles,
|
||||
css`
|
||||
:host {
|
||||
position: relative;
|
||||
display: grid;
|
||||
gap: 1px;
|
||||
}
|
||||
:host([disallow-drop])::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
inset: 0;
|
||||
border: 2px solid var(--uui-color-danger);
|
||||
border-radius: calc(var(--uui-border-radius) * 2);
|
||||
pointer-events: none;
|
||||
}
|
||||
:host([disallow-drop])::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
inset: 0;
|
||||
border-radius: calc(var(--uui-border-radius) * 2);
|
||||
background-color: var(--uui-color-danger);
|
||||
opacity: 0.2;
|
||||
pointer-events: none;
|
||||
}
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { UmbBlockDataType } from '../../block/index.js';
|
||||
import { UMB_BLOCK_CATALOGUE_MODAL, UmbBlockEntriesContext } from '../../block/index.js';
|
||||
import { UMB_BLOCK_GRID_ENTRY_CONTEXT, type UmbBlockGridWorkspaceData } from '../index.js';
|
||||
import type { UmbBlockGridLayoutModel, UmbBlockGridTypeModel } from '../types.js';
|
||||
import type { UmbBlockGridLayoutModel, UmbBlockGridTypeAreaType, UmbBlockGridTypeModel } from '../types.js';
|
||||
import { UMB_BLOCK_GRID_MANAGER_CONTEXT } from './block-grid-manager.context.js';
|
||||
import type { UmbBlockGridScalableContainerContext } from './block-grid-scale-manager/block-grid-scale-manager.controller.js';
|
||||
import { UmbNumberState } from '@umbraco-cms/backoffice/observable-api';
|
||||
@@ -25,6 +25,8 @@ export class UmbBlockGridEntriesContext
|
||||
#layoutColumns = new UmbNumberState(undefined);
|
||||
readonly layoutColumns = this.#layoutColumns.asObservable();
|
||||
|
||||
#areaType?: UmbBlockGridTypeAreaType;
|
||||
|
||||
//#parentUnique?: string;
|
||||
#areaKey?: string | null;
|
||||
|
||||
@@ -71,8 +73,8 @@ export class UmbBlockGridEntriesContext
|
||||
const index = routingInfo.index ? parseInt(routingInfo.index) : -1;
|
||||
return {
|
||||
data: {
|
||||
blocks: [],
|
||||
blockGroups: [],
|
||||
blocks: this.#retrieveAllowedElementTypes(),
|
||||
blockGroups: this._manager?.getBlockGroups() ?? [],
|
||||
openClipboard: routingInfo.view === 'clipboard',
|
||||
blockOriginData: { index: index },
|
||||
},
|
||||
@@ -180,6 +182,7 @@ export class UmbBlockGridEntriesContext
|
||||
this.observe(
|
||||
this.#parentEntry.areaType(this.#areaKey),
|
||||
(areaType) => {
|
||||
this.#areaType = areaType;
|
||||
const hostEl = this.getHostElement() as HTMLElement | undefined;
|
||||
if (!hostEl) return;
|
||||
hostEl.setAttribute('data-area-alias', areaType?.alias ?? '');
|
||||
@@ -229,4 +232,61 @@ export class UmbBlockGridEntriesContext
|
||||
// TODO: Loop through children and delete them as well?
|
||||
await super.delete(contentUdi);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @returns 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) {
|
||||
// Area entries:
|
||||
if (!this.#areaType) return [];
|
||||
|
||||
if (this.#areaType.specifiedAllowance && this.#areaType.specifiedAllowance.length > 0) {
|
||||
return this.#areaType.specifiedAllowance
|
||||
.flatMap((permission) => {
|
||||
if (permission.groupKey) {
|
||||
return (
|
||||
this._manager
|
||||
?.getBlockTypes()
|
||||
.filter(
|
||||
(blockType) => blockType.groupKey === permission.groupKey && blockType.allowInAreas === true,
|
||||
) ?? []
|
||||
);
|
||||
} else if (permission.elementTypeKey) {
|
||||
return (
|
||||
this._manager?.getBlockTypes().filter((x) => x.contentElementTypeKey === permission.elementTypeKey) ??
|
||||
[]
|
||||
);
|
||||
}
|
||||
return [];
|
||||
})
|
||||
.filter((v, i, a) => a.find((x) => x.contentElementTypeKey === v.contentElementTypeKey) === undefined);
|
||||
}
|
||||
|
||||
return this._manager.getBlockTypes().filter((x) => x.allowInAreas);
|
||||
}
|
||||
// If no AreaKey, then we are representing the items of the root:
|
||||
|
||||
// Root entries:
|
||||
return this._manager.getBlockTypes().filter((x) => x.allowAtRoot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given contentUdi is allowed in the current area.
|
||||
* @param contentUdi {string} - The contentUdi of the content to check.
|
||||
* @returns {boolean} - True if the content is allowed in the current area, otherwise false.
|
||||
*/
|
||||
allowDrop(contentUdi: string) {
|
||||
const content = this._manager?.getContentOf(contentUdi);
|
||||
if (!content) return false;
|
||||
|
||||
return (
|
||||
this.#retrieveAllowedElementTypes()
|
||||
.map((x) => x.contentElementTypeKey)
|
||||
.indexOf(content.contentTypeKey) !== -1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +184,6 @@ export class UmbBlockGridEntryContext
|
||||
const hasRelevantColumnSpanOptions = relevantColumnSpanOptions.length > 1;
|
||||
const hasRowSpanOptions = minMaxRowSpan[0] !== minMaxRowSpan[1];
|
||||
const canScale = hasRelevantColumnSpanOptions || hasRowSpanOptions;
|
||||
console.log('canScale calc:', canScale);
|
||||
|
||||
this.#canScale.setValue(canScale);
|
||||
},
|
||||
|
||||
@@ -8,15 +8,15 @@ import {
|
||||
} from '@umbraco-cms/backoffice/property-editor';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import {
|
||||
UMB_BLOCK_GRID_TYPE,
|
||||
type UmbBlockGridTypeGroupType,
|
||||
type UmbBlockGridGroupTypeConfiguration,
|
||||
} from '@umbraco-cms/backoffice/block';
|
||||
import { UMB_BLOCK_GRID_TYPE, type UmbBlockGridTypeGroupType } from '@umbraco-cms/backoffice/block';
|
||||
import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UMB_PROPERTY_DATASET_CONTEXT, type UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property';
|
||||
import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
interface MappedGroupWithBlockTypes extends Partial<UmbBlockGridTypeGroupType> {
|
||||
blocks: Array<UmbBlockTypeWithGroupKey>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @element umb-property-editor-ui-block-grid-type-configuration
|
||||
*/
|
||||
@@ -47,7 +47,7 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
|
||||
private _blockGroups: Array<UmbBlockGridTypeGroupType> = [];
|
||||
|
||||
@state()
|
||||
private _mappedValuesAndGroups: Array<UmbBlockGridGroupTypeConfiguration> = [];
|
||||
private _mappedValuesAndGroups: Array<MappedGroupWithBlockTypes> = [];
|
||||
|
||||
@state()
|
||||
private _workspacePath?: string;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { UmbBlockTypeBaseModel, UmbBlockTypeWithGroupKey } from '../block-type/index.js';
|
||||
import type { UmbBlockTypeWithGroupKey } from '../block-type/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 {
|
||||
export interface UmbBlockGridTypeModel extends UmbBlockTypeWithGroupKey {
|
||||
columnSpanOptions: Array<UmbBlockGridTypeColumnSpanOption>;
|
||||
allowAtRoot: boolean;
|
||||
allowInAreas: boolean;
|
||||
@@ -41,10 +41,6 @@ export interface UmbBlockGridTypeGroupType {
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface UmbBlockGridGroupTypeConfiguration extends Partial<UmbBlockGridTypeGroupType> {
|
||||
blocks: Array<UmbBlockTypeWithGroupKey>;
|
||||
}
|
||||
|
||||
// Content models:
|
||||
|
||||
export interface UmbBlockGridValueModel extends UmbBlockValueType<UmbBlockGridLayoutModel> {}
|
||||
|
||||
@@ -26,7 +26,7 @@ export class UmbBlockListEntriesContext extends UmbBlockEntriesContext<
|
||||
const index = routingInfo.index ? parseInt(routingInfo.index) : -1;
|
||||
return {
|
||||
data: {
|
||||
blocks: [],
|
||||
blocks: this._manager?.getBlockTypes() ?? [],
|
||||
blockGroups: [],
|
||||
openClipboard: routingInfo.view === 'clipboard',
|
||||
blockOriginData: { index: index },
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbBlockListManagerContext } from '../../context/block-list-manager.context.js';
|
||||
import '../../components/block-list-entry/index.js';
|
||||
import type { UmbBlockListEntryElement } from '../../components/block-list-entry/index.js';
|
||||
import type { UmbBlockListLayoutModel, UmbBlockListValueModel } from '../../types.js';
|
||||
import { UmbBlockListEntriesContext } from '../../context/block-list-entries.context.js';
|
||||
import { UMB_BLOCK_LIST_PROPERTY_EDITOR_ALIAS } from './manifests.js';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { html, customElement, property, state, repeat, css } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
|
||||
@@ -90,7 +90,7 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement
|
||||
const useInlineEditingAsDefault = config.getValueByAlias<boolean>('useInlineEditingAsDefault');
|
||||
this.#managerContext.setInlineEditingMode(useInlineEditingAsDefault);
|
||||
// TODO:
|
||||
//config.useSingleBlockMode, not done jey
|
||||
//config.useSingleBlockMode, not done jet
|
||||
this.style.maxWidth = config.getValueByAlias<string>('maxPropertyWidth') ?? '';
|
||||
|
||||
this.#managerContext.setEditorConfiguration(config);
|
||||
|
||||
@@ -2,9 +2,7 @@ import {
|
||||
DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS,
|
||||
type UmbDocumentTypeItemModel,
|
||||
} from '@umbraco-cms/backoffice/document-type';
|
||||
import { UmbDeleteEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import { html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbRepositoryItemsManager } from '@umbraco-cms/backoffice/repository';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
|
||||
@@ -18,27 +16,38 @@ export class UmbBlockTypeCardElement extends UmbLitElement {
|
||||
);
|
||||
|
||||
@property({ type: String, attribute: false })
|
||||
workspacePath?: string;
|
||||
href?: string;
|
||||
|
||||
@property({ type: String, attribute: false })
|
||||
public get key(): string | undefined {
|
||||
return this._key;
|
||||
name?: string;
|
||||
|
||||
@property({ type: String, attribute: false })
|
||||
iconColor?: string;
|
||||
|
||||
@property({ type: String, attribute: false })
|
||||
backgroundColor?: string;
|
||||
|
||||
// TODO: support custom icon/image file
|
||||
|
||||
@property({ type: String, attribute: false })
|
||||
public get contentElementTypeKey(): string | undefined {
|
||||
return this._elementTypeKey;
|
||||
}
|
||||
public set key(value: string | undefined) {
|
||||
this._key = value;
|
||||
public set contentElementTypeKey(value: string | undefined) {
|
||||
this._elementTypeKey = value;
|
||||
if (value) {
|
||||
this.#itemManager.setUniques([value]);
|
||||
} else {
|
||||
this.#itemManager.setUniques([]);
|
||||
}
|
||||
}
|
||||
private _key?: string | undefined;
|
||||
private _elementTypeKey?: string | undefined;
|
||||
|
||||
@state()
|
||||
_name?: string;
|
||||
_fallbackName?: string;
|
||||
|
||||
@state()
|
||||
_icon?: string | null;
|
||||
_fallbackIcon?: string | null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -46,37 +55,22 @@ export class UmbBlockTypeCardElement extends UmbLitElement {
|
||||
this.observe(this.#itemManager.items, (items) => {
|
||||
const item = items[0];
|
||||
if (item) {
|
||||
this._icon = item.icon;
|
||||
this._name = item.name;
|
||||
console.log('got item', item);
|
||||
this._fallbackIcon = item.icon;
|
||||
this._fallbackName = item.name;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#onRequestDelete() {
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, async (modalManager) => {
|
||||
const modalContext = modalManager.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
color: 'danger',
|
||||
headline: `Remove ${this._name}?`,
|
||||
content: 'Are you sure you want to remove this block type?',
|
||||
confirmLabel: 'Remove',
|
||||
},
|
||||
});
|
||||
|
||||
await modalContext?.onSubmit();
|
||||
this.dispatchEvent(new UmbDeleteEvent());
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Support image files instead of icons.
|
||||
render() {
|
||||
return html`
|
||||
<uui-card-block-type href="${this.workspacePath}/edit/${this.key}" .name=${this._name ?? ''}>
|
||||
<uui-icon name=${this._icon ?? ''}></uui-icon>
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button @click=${this.#onRequestDelete} label="Remove block">
|
||||
<uui-icon name="icon-trash"></uui-icon>
|
||||
</uui-button>
|
||||
</uui-action-bar>
|
||||
<uui-card-block-type
|
||||
href=${ifDefined(this.href)}
|
||||
.name=${this.name ?? this._fallbackName ?? ''}
|
||||
.background=${this.backgroundColor}>
|
||||
<uui-icon name=${this._fallbackIcon ?? ''} style="color:${this.iconColor}"></uui-icon>
|
||||
<slot name="actions" slot="actions"> </slot>
|
||||
</uui-card-block-type>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { UmbBlockTypeBaseModel } from '../../types.js';
|
||||
import { UMB_DOCUMENT_TYPE_PICKER_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import {
|
||||
UMB_CONFIRM_MODAL,
|
||||
UMB_DOCUMENT_TYPE_PICKER_MODAL,
|
||||
UMB_MODAL_MANAGER_CONTEXT,
|
||||
} from '@umbraco-cms/backoffice/modal';
|
||||
import '../block-type-card/index.js';
|
||||
import { css, html, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
@@ -74,18 +78,41 @@ export class UmbInputBlockTypeElement<
|
||||
return undefined;
|
||||
}
|
||||
|
||||
#onRequestDelete(item: BlockType) {
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, async (modalManager) => {
|
||||
const modalContext = modalManager.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
color: 'danger',
|
||||
headline: `Remove [TODO: Get name]?`,
|
||||
content: 'Are you sure you want to remove this block type?',
|
||||
confirmLabel: 'Remove',
|
||||
},
|
||||
});
|
||||
|
||||
await modalContext?.onSubmit();
|
||||
this.deleteItem(item.contentElementTypeKey);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<div>
|
||||
${repeat(this.value, (block) => block.contentElementTypeKey, this.#renderItem)} ${this.#renderButton()}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
#renderItem = (item: BlockType) => {
|
||||
#renderItem = (block: BlockType) => {
|
||||
return html`
|
||||
<umb-block-type-card
|
||||
.workspacePath=${this.workspacePath}
|
||||
.key=${item.contentElementTypeKey}
|
||||
@delete=${() => this.deleteItem(item.contentElementTypeKey)}>
|
||||
.name=${block.label}
|
||||
.iconColor=${block.iconColor}
|
||||
.backgroundColor=${block.backgroundColor}
|
||||
.href="${this.workspacePath}/edit/${block.contentElementTypeKey}"
|
||||
.contentElementTypeKey=${block.contentElementTypeKey}>
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button @click=${this.#onRequestDelete} label="Remove block">
|
||||
<uui-icon name="icon-trash"></uui-icon>
|
||||
</uui-button>
|
||||
</uui-action-bar>
|
||||
</umb-block-type-card>
|
||||
`;
|
||||
};
|
||||
|
||||
@@ -9,12 +9,11 @@ export interface UmbBlockTypeBaseModel {
|
||||
iconColor?: string;
|
||||
backgroundColor?: string;
|
||||
editorSize?: UUIModalSidebarSize;
|
||||
icon?: string; // remove later
|
||||
forceHideContentEditorInOverlay: boolean;
|
||||
}
|
||||
|
||||
export interface UmbBlockTypeGroup {
|
||||
name?: string | null;
|
||||
name?: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -141,6 +141,13 @@ export abstract class UmbBlockManagerContext<
|
||||
return this.#settings.asObservablePart((source) => source.find((x) => x.udi === udi));
|
||||
}
|
||||
|
||||
getBlockTypeOf(contentTypeKey: string) {
|
||||
return this.#blockTypes.value.find((x) => x.contentElementTypeKey === contentTypeKey);
|
||||
}
|
||||
getContentOf(contentUdi: string) {
|
||||
return this.#contents.value.find((x) => x.udi === contentUdi);
|
||||
}
|
||||
|
||||
/*setOneLayout(layoutData: BlockLayoutType) {
|
||||
return this._layouts.appendOne(layoutData);
|
||||
}*/
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
UmbBlockTypeGroup,
|
||||
UmbBlockTypeWithGroupKey,
|
||||
} from '@umbraco-cms/backoffice/block';
|
||||
import { css, html, customElement, state, repeat, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, customElement, state, repeat, nothing } from '@umbraco-cms/backoffice/external/lit';
|
||||
import {
|
||||
UMB_MODAL_CONTEXT,
|
||||
UmbModalBaseElement,
|
||||
@@ -58,7 +58,7 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
|
||||
|
||||
const noGroupBlocks = blocks.filter((block) => !blockGroups.find((group) => group.key === block.groupKey));
|
||||
const grouped = blockGroups.map((group) => ({
|
||||
name: group.name ?? '',
|
||||
name: group.name,
|
||||
blocks: blocks.filter((block) => block.groupKey === group.key),
|
||||
}));
|
||||
|
||||
@@ -87,26 +87,28 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
|
||||
|
||||
#renderCreateEmpty() {
|
||||
return html`
|
||||
${this._groupedBlocks.map(
|
||||
(group) => html`
|
||||
${group.name ? html`<h4>${group.name}</h4>` : nothing}
|
||||
<div class="blockGroup">
|
||||
${repeat(
|
||||
group.blocks,
|
||||
(block) => block.contentElementTypeKey,
|
||||
(block) => html`
|
||||
<uui-card-block-type
|
||||
name=${ifDefined(block.label)}
|
||||
background=${ifDefined(block.backgroundColor)}
|
||||
style="color: ${block.iconColor}"
|
||||
href="${this._workspacePath}create/${block.contentElementTypeKey}">
|
||||
<uui-icon .name=${block.icon ?? ''}></uui-icon>
|
||||
</uui-card-block-type>
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
`,
|
||||
)}
|
||||
${this._groupedBlocks
|
||||
? this._groupedBlocks.map(
|
||||
(group) => html`
|
||||
${group.name && group.name !== '' ? html`<h4>${group.name}</h4>` : nothing}
|
||||
<div class="blockGroup">
|
||||
${repeat(
|
||||
group.blocks,
|
||||
(block) => block.contentElementTypeKey,
|
||||
(block) => html`
|
||||
<umb-block-type-card
|
||||
.name=${block.label}
|
||||
.iconColor=${block.iconColor}
|
||||
.backgroundColor=${block.backgroundColor}
|
||||
.contentElementTypeKey=${block.contentElementTypeKey}
|
||||
.href="${this._workspacePath}create/${block.contentElementTypeKey}">
|
||||
</umb-block-type-card>
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
: ''}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -324,6 +324,7 @@ export class UmbBlockWorkspaceContext<
|
||||
};
|
||||
|
||||
public destroy(): void {
|
||||
super.destroy();
|
||||
this.#layout.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,6 @@ export class UmbInputUploadFieldElement extends FormControlMixin(UmbLitElement)
|
||||
}
|
||||
|
||||
#renderFiles() {
|
||||
console.log('files', this._files);
|
||||
return repeat(
|
||||
this._files,
|
||||
(path) => path,
|
||||
|
||||
@@ -74,27 +74,79 @@ export type resolveVerticalDirectionArgs<T, ElementType extends HTMLElement> = {
|
||||
type INTERNAL_UmbSorterConfig<T, ElementType extends HTMLElement> = {
|
||||
getUniqueOfElement: (element: ElementType) => string | null | symbol | number;
|
||||
getUniqueOfModel: (modeEntry: T) => string | null | symbol | number;
|
||||
/**
|
||||
* Optionally define a unique identifier for each sorter experience, all Sorters that uses the same identifier to connect with other sorters.
|
||||
*/
|
||||
identifier: string | symbol;
|
||||
/**
|
||||
* A query selector for the item element.
|
||||
*/
|
||||
itemSelector: string;
|
||||
disabledItemSelector?: string;
|
||||
/**
|
||||
* A selector for the container element, if not defined the host element will be used as container.
|
||||
*/
|
||||
containerSelector: string;
|
||||
/**
|
||||
* A selector for elements to ignore, elements that should not be draggable when within an draggable item, This defaults to links, images & iframes.
|
||||
*/
|
||||
ignorerSelector: string;
|
||||
/**
|
||||
* An class to set on the placeholder element.
|
||||
*/
|
||||
placeholderClass?: string;
|
||||
/**
|
||||
* An attribute to set on the placeholder element.
|
||||
*/
|
||||
placeholderAttr?: string;
|
||||
/**
|
||||
* The selector to find the draggable element within the item.
|
||||
*/
|
||||
draggableSelector?: string;
|
||||
boundarySelector?: string;
|
||||
//boundarySelector?: string;
|
||||
dataTransferResolver?: (dataTransfer: DataTransfer | null, currentItem: T) => void;
|
||||
onStart?: (argument: { item: T; element: ElementType }) => void;
|
||||
/**
|
||||
* This callback is executed every time where is a change to this model, this could be a move, insert or remove.
|
||||
* But notice its not called if a more specific callback is provided, such would be the performItemMove, performItemInsert or performItemRemove or performItemRemove.
|
||||
*/
|
||||
onChange?: (argument: { item: T; model: Array<T> }) => void;
|
||||
onContainerChange?: (argument: { item: T; element: ElementType }) => void;
|
||||
/**
|
||||
* This callback is executed when an item is moved from another container to this container.
|
||||
*/
|
||||
onContainerChange?: (argument: { item: T; model: Array<T>; from: UmbSorterController<T, ElementType> }) => void;
|
||||
onEnd?: (argument: { item: T; element: ElementType }) => void;
|
||||
itemHasNestedContainersResolver?: (element: HTMLElement) => boolean;
|
||||
onDisallowed?: () => void;
|
||||
onAllowed?: () => void;
|
||||
onRequestDrop?: (argument: { item: T }) => boolean | void;
|
||||
resolveVerticalDirection?: (argument: resolveVerticalDirectionArgs<T, ElementType>) => void;
|
||||
/**
|
||||
* Callback when a item move is disallowed.
|
||||
* This should make a visual indication for the user to understand that the move is not allowed.
|
||||
*/
|
||||
onDisallowed?: (argument: { item: T; element: ElementType }) => void;
|
||||
/**
|
||||
* Callback when a item move is allowed.
|
||||
* This should remove any visual indication of the disallowing, reverting the work of the onDisallowed callback.
|
||||
*/
|
||||
onAllowed?: (argument: { item: T; element: ElementType }) => void;
|
||||
/**
|
||||
* Callback when user tries to move an item from another Sorter to this Sorter, return true or false to allow or disallow the move.
|
||||
*/
|
||||
onRequestMove?: (argument: { item: T }) => boolean;
|
||||
/**
|
||||
* This callback is executed when an item is hovered within this container.
|
||||
* The callback should return true if the item should be placed after based on a vertical logic. Other wise false for horizontal. True is default.
|
||||
*/
|
||||
resolveVerticalDirection?: (argument: resolveVerticalDirectionArgs<T, ElementType>) => boolean;
|
||||
/**
|
||||
* This callback is executed when an item is moved within this container.
|
||||
*/
|
||||
performItemMove?: (argument: { item: T; newIndex: number; oldIndex: number }) => Promise<boolean> | boolean;
|
||||
/**
|
||||
* This callback is executed when an item should be inserted into this container.
|
||||
*/
|
||||
performItemInsert?: (argument: { item: T; newIndex: number }) => Promise<boolean> | boolean;
|
||||
/**
|
||||
* This callback is executed when an item should be removed from this container.
|
||||
*/
|
||||
performItemRemove?: (argument: { item: T }) => Promise<boolean> | boolean;
|
||||
};
|
||||
|
||||
@@ -113,6 +165,9 @@ export type UmbSorterConfig<T, ElementType extends HTMLElement = HTMLElement> =
|
||||
*/
|
||||
export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElement> implements UmbController {
|
||||
//
|
||||
// The sorter who last indicated that it was okay or not okay to drop here:
|
||||
static lastIndicationSorter?: UmbSorterController<unknown>;
|
||||
|
||||
// A sorter that is requested to become the next sorter:
|
||||
static originalSorter?: UmbSorterController<unknown>;
|
||||
static originalIndex?: number;
|
||||
@@ -144,8 +199,6 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
#dragX = 0;
|
||||
#dragY = 0;
|
||||
|
||||
#lastIndicationContainerCtrl: UmbSorterController<T, ElementType> | null = null;
|
||||
|
||||
public get controllerAlias() {
|
||||
// We only support one Sorter Controller pr. Controller Host.
|
||||
return 'umbSorterController';
|
||||
@@ -280,7 +333,7 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
setupIgnorerElements(element, this.#config.ignorerSelector);
|
||||
}
|
||||
|
||||
if (!this.#config.disabledItemSelector || !element.matches('> ' + this.#config.disabledItemSelector)) {
|
||||
if (!this.#config.disabledItemSelector || !element.matches(this.#config.disabledItemSelector)) {
|
||||
// Idea: to make sure on does not get initialized twice: if ((element as HTMLElement).draggable === true) return;
|
||||
(element as HTMLElement).draggable = true;
|
||||
element.addEventListener('dragstart', this.#handleDragStart);
|
||||
@@ -744,6 +797,11 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
const newModel = [...this.#model];
|
||||
newModel.splice(newIndex, 0, item);
|
||||
this.#model = newModel;
|
||||
this.#config.onContainerChange?.({
|
||||
model: newModel,
|
||||
item,
|
||||
from: fromCtrl as unknown as UmbSorterController<T, ElementType>,
|
||||
});
|
||||
this.#config.onChange?.({ model: newModel, item });
|
||||
}
|
||||
|
||||
@@ -756,30 +814,26 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
}
|
||||
|
||||
updateAllowIndication(item: T) {
|
||||
// TODO: Allow indication.
|
||||
/*
|
||||
// Remove old indication:
|
||||
if (this.#lastIndicationContainerCtrl !== null && this.#lastIndicationContainerCtrl !== controller) {
|
||||
this.#lastIndicationContainerCtrl.notifyAllowed();
|
||||
if (UmbSorterController.lastIndicationSorter && UmbSorterController.lastIndicationSorter !== (this as unknown)) {
|
||||
UmbSorterController.lastIndicationSorter.notifyAllowed();
|
||||
}
|
||||
this.#lastIndicationContainerCtrl = controller;
|
||||
UmbSorterController.lastIndicationSorter = this as unknown as UmbSorterController<unknown>;
|
||||
|
||||
if (controller.notifyRequestDrop({ item: item }) === true) {
|
||||
controller.notifyAllowed();
|
||||
if (this.notifyRequestDrop({ item: item }) === true) {
|
||||
this.notifyAllowed();
|
||||
return true;
|
||||
}
|
||||
|
||||
controller.notifyDisallowed(); // This block is not accepted to we will indicate that its not allowed.
|
||||
this.notifyDisallowed(); // This block is not accepted to we will indicate that its not allowed.
|
||||
return false;
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
removeAllowIndication() {
|
||||
// Remove old indication:
|
||||
if (this.#lastIndicationContainerCtrl !== null) {
|
||||
this.#lastIndicationContainerCtrl.notifyAllowed();
|
||||
if (UmbSorterController.lastIndicationSorter) {
|
||||
UmbSorterController.lastIndicationSorter.notifyAllowed();
|
||||
}
|
||||
this.#lastIndicationContainerCtrl = null;
|
||||
UmbSorterController.lastIndicationSorter = undefined;
|
||||
}
|
||||
|
||||
// TODO: Move auto scroll into its own class?
|
||||
@@ -844,17 +898,23 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
|
||||
public notifyDisallowed() {
|
||||
if (this.#config.onDisallowed) {
|
||||
this.#config.onDisallowed();
|
||||
this.#config.onDisallowed({
|
||||
item: UmbSorterController.activeItem,
|
||||
element: UmbSorterController.activeElement! as ElementType,
|
||||
});
|
||||
}
|
||||
}
|
||||
public notifyAllowed() {
|
||||
if (this.#config.onAllowed) {
|
||||
this.#config.onAllowed();
|
||||
this.#config.onAllowed({
|
||||
item: UmbSorterController.activeItem,
|
||||
element: UmbSorterController.activeElement! as ElementType,
|
||||
});
|
||||
}
|
||||
}
|
||||
public notifyRequestDrop(data: any) {
|
||||
if (this.#config.onRequestDrop) {
|
||||
return this.#config.onRequestDrop(data) || false;
|
||||
if (this.#config.onRequestMove) {
|
||||
return this.#config.onRequestMove(data) || false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -865,7 +925,7 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
|
||||
this.#handleDragEnd();
|
||||
}
|
||||
|
||||
this.#lastIndicationContainerCtrl = null;
|
||||
UmbSorterController.lastIndicationSorter = undefined;
|
||||
|
||||
// TODO: Clean up items??
|
||||
this.#observer.disconnect();
|
||||
|
||||
Reference in New Issue
Block a user