Merge branch 'main' into improvement/model-remapping
This commit is contained in:
@@ -62,6 +62,7 @@
|
||||
"./package": "./dist-cms/packages/packages/package/index.js",
|
||||
"./data-type": "./dist-cms/packages/core/data-type/index.js",
|
||||
"./language": "./dist-cms/packages/language/index.js",
|
||||
"./dynamic-root": "./dist-cms/packages/dynamic-root/index.js",
|
||||
"./logviewer": "./dist-cms/packages/settings/logviewer/index.js",
|
||||
"./relation-type": "./dist-cms/packages/relations/relation-types/index.js",
|
||||
"./relation": "./dist-cms/packages/relations/relations/index.js",
|
||||
|
||||
@@ -32,6 +32,11 @@ const CORE_PACKAGES = [
|
||||
import('../../packages/umbraco-news/umbraco-package.js'),
|
||||
import('../../packages/user/umbraco-package.js'),
|
||||
import('../../packages/models-builder/umbraco-package.js'),
|
||||
import('../../packages/tags/umbraco-package.js'),
|
||||
import('../../packages/log-viewer/umbraco-package.js'),
|
||||
import('../../packages/health-check/umbraco-package.js'),
|
||||
import('../../packages/static-file/umbraco-package.js'),
|
||||
import('../../packages/dynamic-root/umbraco-package.js'),
|
||||
];
|
||||
|
||||
@customElement('umb-backoffice')
|
||||
|
||||
@@ -1156,6 +1156,38 @@ export default {
|
||||
pickedTrashedItem: 'Du har valgt et dokument som er slettet eller lagt i papirkurven',
|
||||
pickedTrashedItems: 'Du har valgt dokumenter som er slettede eller lagt i papirkurven',
|
||||
},
|
||||
dynamicRoot: {
|
||||
configurationTitle: 'Dynamisk udgangspunkts forespørgsel',
|
||||
pickDynamicRootOriginTitle: 'Vælg begyndelsen',
|
||||
pickDynamicRootOriginDesc: 'Beskriv begyndelsen for dynamisk udgangspunkts forespørgselen',
|
||||
originRootTitle: 'Roden',
|
||||
originRootDesc: 'Rod noden for denne kilde',
|
||||
originParentTitle: 'Overliggende',
|
||||
originParentDesc: 'Den overliggende node af kilden i denne redigerings session',
|
||||
originCurrentTitle: 'Nuværende',
|
||||
originCurrentDesc: 'Kilde noden for denne redigerings session',
|
||||
originSiteTitle: 'Siden',
|
||||
originSiteDesc: 'Nærmeste node med et domæne',
|
||||
originByKeyTitle: 'Specifik Node',
|
||||
originByKeyDesc: 'Vælg en specifik Node',
|
||||
pickDynamicRootQueryStepTitle: 'Tilføj skridt til forespørgsel',
|
||||
pickDynamicRootQueryStepDesc: 'Specificer næste skridt i din dynamisk udgangspunkts forespørgsel',
|
||||
queryStepNearestAncestorOrSelfTitle: 'Nærmeste forældre eller selv',
|
||||
queryStepNearestAncestorOrSelfDesc: 'Forespørg the nærmeste forældre eller selv der passer på en af de givne typer',
|
||||
queryStepFurthestAncestorOrSelfTitle: 'Fjerneste forældre eller selv',
|
||||
queryStepFurthestAncestorOrSelfDesc: 'Forespørg fjerneste forældre eller selv der passer på en af de givne typer',
|
||||
queryStepNearestDescendantOrSelfTitle: 'Nærmeste barn eller selv',
|
||||
queryStepNearestDescendantOrSelfDesc: 'Forespørg nærmeste barn eller selv der passer på en af de givne typer',
|
||||
queryStepFurthestDescendantOrSelfTitle: 'Fjerneste barn eller selv',
|
||||
queryStepFurthestDescendantOrSelfDesc: 'Forespørg fjerneste barn eller selv der passer på en af de givne typer',
|
||||
queryStepCustomTitle: 'Brugerdefineret',
|
||||
queryStepCustomDesc: 'Forespørg med et skræddersyet forespørgsels skridt',
|
||||
addQueryStep: 'Tilføj skridt',
|
||||
queryStepTypes: 'der passer med typerne: ',
|
||||
noValidStartNodeTitle: 'Intet passende indhold',
|
||||
noValidStartNodeDesc:
|
||||
'Konfigurationen af dette felt passer ikke med noget indhold. Opret det manglende indhold eller kontakt din adminnistrator for at tilpasse Dynamisk Udgangspunkts Forespørgselen for dette felt.',
|
||||
},
|
||||
mediaPicker: {
|
||||
deletedItem: 'Slettet medie',
|
||||
pickedTrashedItem: 'Du har valgt et medie som er slettet eller lagt i papirkurven',
|
||||
|
||||
@@ -1148,11 +1148,46 @@ export default {
|
||||
},
|
||||
contentPicker: {
|
||||
allowedItemTypes: 'You can only select items of type(s): %0%',
|
||||
defineDynamicRoot: 'Specify a Dynamic Root',
|
||||
defineDynamicRoot: 'Specify root node',
|
||||
defineRootNode: 'Pick root node',
|
||||
pickedTrashedItem: 'You have picked a content item currently deleted or in the recycle bin',
|
||||
pickedTrashedItems: 'You have picked content items currently deleted or in the recycle bin',
|
||||
},
|
||||
dynamicRoot: {
|
||||
configurationTitle: 'Dynamic Root Query',
|
||||
pickDynamicRootOriginTitle: 'Pick origin',
|
||||
pickDynamicRootOriginDesc: 'Define the origin for your Dynamic Root Query',
|
||||
originRootTitle: 'Root',
|
||||
originRootDesc: 'Root node of this editing session',
|
||||
originParentTitle: 'Parent',
|
||||
originParentDesc: 'The parent node of the source in this editing session',
|
||||
originCurrentTitle: 'Current',
|
||||
originCurrentDesc: 'The content node that is source for this editing session',
|
||||
originSiteTitle: 'Site',
|
||||
originSiteDesc: 'Find nearest node with a hostname',
|
||||
originByKeyTitle: 'Specific Node',
|
||||
originByKeyDesc: 'Pick a specific Node as the origin for this query',
|
||||
pickDynamicRootQueryStepTitle: 'Append step to query',
|
||||
pickDynamicRootQueryStepDesc: 'Define the next step of your Dynamic Root Query',
|
||||
queryStepNearestAncestorOrSelfTitle: 'Nearest Ancestor Or Self',
|
||||
queryStepNearestAncestorOrSelfDesc: 'Query the nearest ancestor or self that fits with one of the configured types',
|
||||
queryStepFurthestAncestorOrSelfTitle: 'Furthest Ancestor Or Self',
|
||||
queryStepFurthestAncestorOrSelfDesc:
|
||||
'Query the Furthest ancestor or self that fits with one of the configured types',
|
||||
queryStepNearestDescendantOrSelfTitle: 'Nearest Descendant Or Self',
|
||||
queryStepNearestDescendantOrSelfDesc:
|
||||
'Query the nearest descendant or self that fits with one of the configured types',
|
||||
queryStepFurthestDescendantOrSelfTitle: 'Furthest Descendant Or Self',
|
||||
queryStepFurthestDescendantOrSelfDesc:
|
||||
'Query the Furthest descendant or self that fits with one of the configured types',
|
||||
queryStepCustomTitle: 'Custom',
|
||||
queryStepCustomDesc: 'Query the using a custom Query Step',
|
||||
addQueryStep: 'Add query step',
|
||||
queryStepTypes: 'That matches types: ',
|
||||
noValidStartNodeTitle: 'No matching content',
|
||||
noValidStartNodeDesc:
|
||||
'The configuration of this property does not match any content. Create the missing content or contact your administrator to adjust the Dynamic Root settings for this property.',
|
||||
},
|
||||
mediaPicker: {
|
||||
deletedItem: 'Deleted item',
|
||||
pickedTrashedItem: 'You have picked a media item currently deleted or in the recycle bin',
|
||||
|
||||
@@ -5,6 +5,7 @@ import { handlers as dataTypeHandlers } from './handlers/data-type/index.js';
|
||||
import { handlers as dictionaryHandlers } from './handlers/dictionary/index.js';
|
||||
import { handlers as documentHandlers } from './handlers/document/index.js';
|
||||
import { handlers as documentTypeHandlers } from './handlers/document-type/index.js';
|
||||
import { handlers as dynamicRootHandlers } from './handlers/dynamic-root.handlers.js';
|
||||
import { handlers as examineManagementHandlers } from './handlers/examine-management.handlers.js';
|
||||
import { handlers as healthCheckHandlers } from './handlers/health-check.handlers.js';
|
||||
import { handlers as installHandlers } from './handlers/install.handlers.js';
|
||||
@@ -45,6 +46,7 @@ const handlers = [
|
||||
...dictionaryHandlers,
|
||||
...documentHandlers,
|
||||
...documentTypeHandlers,
|
||||
...dynamicRootHandlers,
|
||||
...examineManagementHandlers,
|
||||
...healthCheckHandlers,
|
||||
...installHandlers,
|
||||
|
||||
@@ -225,6 +225,15 @@ export const data: Array<UmbMockDataTypeModel> = [
|
||||
value: {
|
||||
type: 'content',
|
||||
id: null,
|
||||
dynamicRoot: {
|
||||
originAlias: 'Root',
|
||||
querySteps: [
|
||||
{
|
||||
alias: 'FurthestAncestorOrSelf',
|
||||
anyOfDocTypeKeys: ['all-property-editors-document-type-id'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { umbDocumentMockDb } from '../data/document/document.db.js';
|
||||
import type { DynamicRootRequestModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
const { rest } = window.MockServiceWorker;
|
||||
|
||||
export const handlers = [
|
||||
rest.post<DynamicRootRequestModel>(umbracoPath('/dynamic-root/query'), async (req, res, ctx) => {
|
||||
const response = umbDocumentMockDb.tree
|
||||
.getRoot()
|
||||
.items.map((item) => item.id)
|
||||
.slice(0, 1);
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
];
|
||||
@@ -1,108 +1,154 @@
|
||||
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
|
||||
import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { html, customElement, property, state, css } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
import type { UmbRoute, UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router';
|
||||
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 {
|
||||
UMB_BLOCK_CATALOGUE_MODAL,
|
||||
type UmbBlockLayoutBaseModel,
|
||||
type UmbBlockTypeBaseModel,
|
||||
type UmbBlockTypeGroup,
|
||||
} from '@umbraco-cms/backoffice/block';
|
||||
import { type UmbModalRouteBuilder, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal';
|
||||
import type { NumberRangeValueType } from '@umbraco-cms/backoffice/models';
|
||||
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
|
||||
|
||||
/**
|
||||
* @element umb-property-editor-ui-block-grid
|
||||
*/
|
||||
@customElement('umb-property-editor-ui-block-grid')
|
||||
export class UmbPropertyEditorUIBlockGridElement extends UmbLitElement implements UmbPropertyEditorUiElement {
|
||||
#catalogueModal: UmbModalRouteRegistrationController<typeof UMB_BLOCK_CATALOGUE_MODAL.DATA, undefined>;
|
||||
|
||||
@property()
|
||||
value = '';
|
||||
|
||||
@state()
|
||||
private _limitMin?: number;
|
||||
@state()
|
||||
private _limitMax?: number;
|
||||
|
||||
@state()
|
||||
private _blocks?: Array<UmbBlockTypeBaseModel>;
|
||||
|
||||
@state()
|
||||
private _blockGroups?: Array<UmbBlockTypeGroup>;
|
||||
|
||||
@state()
|
||||
private _layouts: Array<UmbBlockLayoutBaseModel> = [];
|
||||
|
||||
@state()
|
||||
private _catalogueRouteBuilder?: UmbModalRouteBuilder;
|
||||
|
||||
@state()
|
||||
private _directRoute?: string;
|
||||
|
||||
@state()
|
||||
private _createButtonLabel = this.localize.term('blockEditor_addBlock');
|
||||
|
||||
@property({ attribute: false })
|
||||
public config?: UmbPropertyEditorConfigCollection;
|
||||
public set config(config: UmbPropertyEditorConfigCollection | undefined) {
|
||||
if (!config) return;
|
||||
|
||||
@state()
|
||||
private _routes: UmbRoute[] = [];
|
||||
const validationLimit = config.getValueByAlias<NumberRangeValueType>('validationLimit');
|
||||
|
||||
@state()
|
||||
private _routerPath: string | undefined;
|
||||
this._limitMin = validationLimit?.min;
|
||||
this._limitMax = validationLimit?.max;
|
||||
|
||||
@state()
|
||||
private _activePath: string | undefined;
|
||||
this._blocks = config.getValueByAlias<Array<UmbBlockTypeBaseModel>>('blocks') ?? [];
|
||||
this._blockGroups = config.getValueByAlias<Array<UmbBlockTypeGroup>>('blockGroups') ?? [];
|
||||
|
||||
@state()
|
||||
private _variantId?: UmbVariantId;
|
||||
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 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);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeContext(UMB_PROPERTY_CONTEXT, (context) => {
|
||||
this.observe(context?.variantId, (propertyVariantId) => {
|
||||
this._variantId = propertyVariantId;
|
||||
this.setupRoutes();
|
||||
this.consumeContext(UMB_PROPERTY_CONTEXT, (propertyContext) => {
|
||||
this.observe(
|
||||
propertyContext?.alias,
|
||||
(alias) => {
|
||||
this.#catalogueModal.setUniquePathValue('propertyAlias', alias);
|
||||
},
|
||||
'observePropertyAlias',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setupRoutes() {
|
||||
this._routes = [];
|
||||
if (this._variantId !== undefined) {
|
||||
this._routes = [
|
||||
{
|
||||
path: 'modal-1',
|
||||
component: () => {
|
||||
return import('./property-editor-ui-block-grid-inner-test.element.js');
|
||||
this.#catalogueModal = new UmbModalRouteRegistrationController(this, UMB_BLOCK_CATALOGUE_MODAL)
|
||||
.addUniquePaths(['propertyAlias'])
|
||||
.addAdditionalPath(':view/:index')
|
||||
.onSetup((routingInfo) => {
|
||||
const index = routingInfo.index ? parseInt(routingInfo.index) : -1;
|
||||
return {
|
||||
data: {
|
||||
blocks: this._blocks ?? [],
|
||||
blockGroups: this._blockGroups ?? [],
|
||||
openClipboard: routingInfo.view === 'clipboard',
|
||||
blockOriginData: { index: index },
|
||||
},
|
||||
setup: (component) => {
|
||||
if (component instanceof HTMLElement) {
|
||||
(component as any).name = 'block-grid-1';
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'modal-2',
|
||||
component: () => {
|
||||
return import('./property-editor-ui-block-grid-inner-test.element.js');
|
||||
},
|
||||
setup: (component) => {
|
||||
if (component instanceof HTMLElement) {
|
||||
(component as any).name = 'block-grid-2';
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
};
|
||||
})
|
||||
.observeRouteBuilder((routeBuilder) => {
|
||||
this._catalogueRouteBuilder = routeBuilder;
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return this._variantId
|
||||
? html`<div>
|
||||
umb-property-editor-ui-block-grid, inner routing test:
|
||||
|
||||
<uui-tab-group slot="navigation">
|
||||
<uui-tab
|
||||
label="TAB 1"
|
||||
href="${this._routerPath + '/'}modal-1"
|
||||
.active=${this._routerPath + '/' + 'modal-1' === this._activePath}></uui-tab>
|
||||
<uui-tab
|
||||
label="TAB 2"
|
||||
href="${this._routerPath + '/'}modal-2"
|
||||
.active=${this._routerPath + '/' + 'modal-2' === this._activePath}></uui-tab>
|
||||
</uui-tab-group>
|
||||
|
||||
<umb-variant-router-slot
|
||||
.variantId=${[this._variantId]}
|
||||
id="router-slot"
|
||||
.routes="${this._routes}"
|
||||
@init=${(event: UmbRouterSlotInitEvent) => {
|
||||
this._routerPath = event.target.absoluteRouterPath;
|
||||
}}
|
||||
@change=${(event: UmbRouterSlotChangeEvent) => {
|
||||
this._activePath = event.target.localActiveViewPath;
|
||||
}}>
|
||||
</umb-variant-router-slot>
|
||||
</div>`
|
||||
: 'loading...';
|
||||
if (this._blocks?.length === 1) {
|
||||
const elementKey = this._blocks[0].contentElementTypeKey;
|
||||
this._directRoute =
|
||||
this._catalogueRouteBuilder?.({ view: 'create', index: -1 }) + 'modal/umb-modal-workspace/create/' + elementKey;
|
||||
}
|
||||
return html`<uui-button-group>
|
||||
<uui-button
|
||||
id="add-button"
|
||||
look="placeholder"
|
||||
label=${this._createButtonLabel}
|
||||
href=${this._directRoute ?? this._catalogueRouteBuilder?.({ view: 'create', index: -1 }) ?? ''}></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];
|
||||
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 UmbPropertyEditorUIBlockGridElement;
|
||||
|
||||
@@ -15,6 +15,7 @@ import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/mod
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
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 {}
|
||||
|
||||
@@ -45,6 +46,8 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement
|
||||
},
|
||||
});
|
||||
|
||||
#catalogueModal: UmbModalRouteRegistrationController<typeof UMB_BLOCK_CATALOGUE_MODAL.DATA, undefined>;
|
||||
|
||||
private _value: UmbBlockListValueModel = {
|
||||
layout: {},
|
||||
contentData: [],
|
||||
@@ -67,9 +70,13 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement
|
||||
this.#context.setSettings(buildUpValue.settingsData);
|
||||
}
|
||||
|
||||
@state()
|
||||
private _createButtonLabel = this.localize.term('content_createEmpty');
|
||||
|
||||
@property({ attribute: false })
|
||||
public set config(config: UmbPropertyEditorConfigCollection | undefined) {
|
||||
if (!config) return;
|
||||
|
||||
const validationLimit = config.getValueByAlias<NumberRangeValueType>('validationLimit');
|
||||
|
||||
this._limitMin = validationLimit?.min;
|
||||
@@ -78,6 +85,13 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement
|
||||
const blocks = config.getValueByAlias<Array<UmbBlockTypeBaseModel>>('blocks') ?? [];
|
||||
this.#context.setBlockTypes(blocks);
|
||||
|
||||
const customCreateButtonLabel = config.getValueByAlias<string>('createLabel');
|
||||
if (customCreateButtonLabel) {
|
||||
this._createButtonLabel = customCreateButtonLabel;
|
||||
} else if (blocks.length === 1) {
|
||||
this._createButtonLabel = `${this.localize.term('general_add')} ${blocks[0].label}`;
|
||||
}
|
||||
|
||||
const useInlineEditingAsDefault = config.getValueByAlias<boolean>('useInlineEditingAsDefault');
|
||||
this.#context.setInlineEditingMode(useInlineEditingAsDefault);
|
||||
//config.useSingleBlockMode
|
||||
@@ -97,16 +111,29 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement
|
||||
private _blocks?: Array<UmbBlockTypeBaseModel>;
|
||||
|
||||
@state()
|
||||
_layouts: Array<UmbBlockLayoutBaseModel> = [];
|
||||
private _layouts: Array<UmbBlockLayoutBaseModel> = [];
|
||||
|
||||
@state()
|
||||
_catalogueRouteBuilder?: UmbModalRouteBuilder;
|
||||
private _catalogueRouteBuilder?: UmbModalRouteBuilder;
|
||||
|
||||
@state()
|
||||
private _directRoute?: string;
|
||||
|
||||
#context = new UmbBlockListManagerContext(this);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeContext(UMB_PROPERTY_CONTEXT, (propertyContext) => {
|
||||
this.observe(
|
||||
propertyContext?.alias,
|
||||
(alias) => {
|
||||
this.#catalogueModal.setUniquePathValue('propertyAlias', alias);
|
||||
},
|
||||
'observePropertyAlias',
|
||||
);
|
||||
});
|
||||
|
||||
// TODO: Prevent initial notification from these observes:
|
||||
this.observe(this.#context.layouts, (layouts) => {
|
||||
this._value = { ...this._value, layout: { [UMB_BLOCK_LIST_PROPERTY_EDITOR_ALIAS]: layouts } };
|
||||
@@ -133,7 +160,8 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement
|
||||
this._blocks = blockTypes;
|
||||
});
|
||||
|
||||
new UmbModalRouteRegistrationController(this, UMB_BLOCK_CATALOGUE_MODAL)
|
||||
this.#catalogueModal = new UmbModalRouteRegistrationController(this, UMB_BLOCK_CATALOGUE_MODAL)
|
||||
.addUniquePaths(['propertyAlias'])
|
||||
.addAdditionalPath(':view/:index')
|
||||
.onSetup((routingInfo) => {
|
||||
const index = routingInfo.index ? parseInt(routingInfo.index) : -1;
|
||||
@@ -151,6 +179,11 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this._blocks?.length === 1) {
|
||||
const elementKey = this._blocks[0].contentElementTypeKey;
|
||||
this._directRoute =
|
||||
this._catalogueRouteBuilder?.({ view: 'create', index: -1 }) + 'modal/umb-modal-workspace/create/' + elementKey;
|
||||
}
|
||||
return html` ${repeat(
|
||||
this._layouts,
|
||||
(x) => x.contentUdi,
|
||||
@@ -164,10 +197,8 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement
|
||||
<uui-button
|
||||
id="add-button"
|
||||
look="placeholder"
|
||||
label=${this.localize.term('content_createEmpty')}
|
||||
href=${this._catalogueRouteBuilder?.({ view: 'create', index: -1 }) ?? ''}>
|
||||
${this.localize.term('content_createEmpty')}
|
||||
</uui-button>
|
||||
label=${this._createButtonLabel}
|
||||
href=${this._directRoute ?? this._catalogueRouteBuilder?.({ view: 'create', index: -1 }) ?? ''}></uui-button>
|
||||
<uui-button
|
||||
label=${this.localize.term('content_createFromClipboard')}
|
||||
look="placeholder"
|
||||
|
||||
@@ -2,10 +2,10 @@ import { UMB_BLOCK_WORKSPACE_MODAL } from '../../workspace/index.js';
|
||||
import type {
|
||||
UmbBlockCatalogueModalData,
|
||||
UmbBlockCatalogueModalValue,
|
||||
UmbBlockTypeGroup,
|
||||
UmbBlockTypeWithGroupKey,
|
||||
} from '@umbraco-cms/backoffice/block';
|
||||
import { css, html, customElement, state, repeat, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { groupBy } from '@umbraco-cms/backoffice/external/lodash';
|
||||
import {
|
||||
UMB_MODAL_CONTEXT,
|
||||
UmbModalBaseElement,
|
||||
@@ -18,10 +18,7 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
|
||||
UmbBlockCatalogueModalValue
|
||||
> {
|
||||
@state()
|
||||
private _blocks: Array<UmbBlockTypeWithGroupKey> = [];
|
||||
|
||||
@state()
|
||||
private _blockGroups: Array<{ key: string; name: string }> = [];
|
||||
private _groupedBlocks: Array<{ name?: string; blocks: Array<UmbBlockTypeWithGroupKey> }> = [];
|
||||
|
||||
@state()
|
||||
_openClipboard?: boolean;
|
||||
@@ -55,16 +52,18 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
|
||||
if (!this.data) return;
|
||||
|
||||
this._openClipboard = this.data.openClipboard ?? false;
|
||||
this._blocks = this.data.blocks ?? [];
|
||||
this._blockGroups = this.data.blockGroups ?? [];
|
||||
}
|
||||
|
||||
/*
|
||||
#onClickBlock(contentElementTypeKey: string) {
|
||||
this.modalContext?.updateValue({ key: contentElementTypeKey });
|
||||
this.modalContext?.submit();
|
||||
const blocks: Array<UmbBlockTypeWithGroupKey> = this.data.blocks ?? [];
|
||||
const blockGroups: Array<UmbBlockTypeGroup> = this.data.blockGroups ?? [];
|
||||
|
||||
const noGroupBlocks = blocks.filter((block) => !blockGroups.find((group) => group.key === block.groupKey));
|
||||
const grouped = blockGroups.map((group) => ({
|
||||
name: group.name ?? '',
|
||||
blocks: blocks.filter((block) => block.groupKey === group.key),
|
||||
}));
|
||||
|
||||
this._groupedBlocks = [{ blocks: noGroupBlocks }, ...grouped];
|
||||
}
|
||||
*/
|
||||
|
||||
render() {
|
||||
return html`
|
||||
@@ -87,17 +86,10 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
|
||||
}
|
||||
|
||||
#renderCreateEmpty() {
|
||||
const blockArrays = groupBy(this._blocks, 'groupKey');
|
||||
|
||||
const mappedGroupsAndBlocks = Object.entries(blockArrays).map(([key, value]) => {
|
||||
const group = this._blockGroups.find((group) => group.key === key);
|
||||
return { name: group?.name, blocks: value };
|
||||
});
|
||||
|
||||
return html`
|
||||
${mappedGroupsAndBlocks.map(
|
||||
${this._groupedBlocks.map(
|
||||
(group) => html`
|
||||
${group.name ? html`<h2>${group.name}</h2>` : nothing}
|
||||
${group.name ? html`<h4>${group.name}</h4>` : nothing}
|
||||
<div class="blockGroup">
|
||||
${repeat(
|
||||
group.blocks,
|
||||
@@ -121,12 +113,18 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
|
||||
#renderViews() {
|
||||
return html`
|
||||
<uui-tab-group slot="navigation">
|
||||
<uui-tab label="Create Empty" ?active=${!this._openClipboard} @click=${() => (this._openClipboard = false)}>
|
||||
Create Empty
|
||||
<uui-tab
|
||||
label=${this.localize.term('blockEditor_tabCreateEmpty')}
|
||||
?active=${!this._openClipboard}
|
||||
@click=${() => (this._openClipboard = false)}>
|
||||
<umb-localize key=${this.localize.term('blockEditor_tabCreateEmpty')}>Create Empty</umb-localize>
|
||||
<uui-icon slot="icon" name="icon-add"></uui-icon>
|
||||
</uui-tab>
|
||||
<uui-tab label="Clipboard" ?active=${this._openClipboard} @click=${() => (this._openClipboard = true)}>
|
||||
Clipboard
|
||||
<uui-tab
|
||||
label=${this.localize.term('blockEditor_tabClipboard')}
|
||||
?active=${this._openClipboard}
|
||||
@click=${() => (this._openClipboard = true)}>
|
||||
<umb-localize key=${this.localize.term('blockEditor_tabClipboard')}>Clipboard</umb-localize>
|
||||
<uui-icon slot="icon" name="icon-paste-in"></uui-icon>
|
||||
</uui-tab>
|
||||
</uui-tab-group>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { UmbBlockTypeBaseModel, UmbBlockWorkspaceData } from '@umbraco-cms/backoffice/block';
|
||||
import type { UmbBlockTypeBaseModel, UmbBlockTypeGroup, UmbBlockWorkspaceData } from '@umbraco-cms/backoffice/block';
|
||||
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
export interface UmbBlockCatalogueModalData {
|
||||
blocks: Array<UmbBlockTypeBaseModel>;
|
||||
blockGroups?: Array<{ name: string; key: string }>;
|
||||
blockGroups?: Array<UmbBlockTypeGroup>;
|
||||
openClipboard?: boolean;
|
||||
blockOriginData: UmbBlockWorkspaceData['originData'];
|
||||
}
|
||||
|
||||
@@ -6,21 +6,23 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
|
||||
export type UmbTreePickerSource = {
|
||||
type?: UmbTreePickerSourceType;
|
||||
id?: string | null;
|
||||
dynamicRoot?: UmbTreePickerDynamicRoot | null;
|
||||
type: UmbTreePickerSourceType;
|
||||
id?: string;
|
||||
dynamicRoot?: UmbTreePickerDynamicRoot;
|
||||
};
|
||||
|
||||
export type UmbTreePickerSourceType = 'content' | 'member' | 'media';
|
||||
|
||||
export type UmbTreePickerDynamicRoot = {
|
||||
originAlias: string;
|
||||
querySteps?: Array<UmbTreePickerDynamicRootQueryStep> | null;
|
||||
originKey?: string;
|
||||
querySteps?: Array<UmbTreePickerDynamicRootQueryStep>;
|
||||
};
|
||||
|
||||
export type UmbTreePickerDynamicRootQueryStep = {
|
||||
unique: string;
|
||||
alias: string;
|
||||
anyOfDocTypeKeys: Array<string>;
|
||||
anyOfDocTypeKeys?: Array<string>;
|
||||
};
|
||||
|
||||
@customElement('umb-input-tree-picker-source')
|
||||
@@ -29,33 +31,33 @@ export class UmbInputTreePickerSourceElement extends FormControlMixin(UmbLitElem
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _type: UmbTreePickerSource['type'] = 'content';
|
||||
#type: UmbTreePickerSourceType = 'content';
|
||||
|
||||
@property()
|
||||
public set type(value: UmbTreePickerSource['type']) {
|
||||
public set type(value: UmbTreePickerSourceType) {
|
||||
if (value === undefined) {
|
||||
value = this._type;
|
||||
value = this.#type;
|
||||
}
|
||||
|
||||
const oldValue = this._type;
|
||||
const oldValue = this.#type;
|
||||
|
||||
this._options = this._options.map((option) =>
|
||||
option.value === value ? { ...option, selected: true } : { ...option, selected: false },
|
||||
);
|
||||
|
||||
this._type = value;
|
||||
this.#type = value;
|
||||
|
||||
this.requestUpdate('type', oldValue);
|
||||
}
|
||||
public get type(): UmbTreePickerSource['type'] {
|
||||
return this._type;
|
||||
public get type(): UmbTreePickerSourceType {
|
||||
return this.#type;
|
||||
}
|
||||
|
||||
@property({ attribute: 'node-id' })
|
||||
nodeId?: string | null;
|
||||
nodeId?: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
dynamicRoot?: UmbTreePickerDynamicRoot | null;
|
||||
dynamicRoot?: UmbTreePickerDynamicRoot | undefined;
|
||||
|
||||
@state()
|
||||
_options: Array<Option> = [
|
||||
@@ -64,20 +66,42 @@ export class UmbInputTreePickerSourceElement extends FormControlMixin(UmbLitElem
|
||||
{ value: 'member', name: 'Members' },
|
||||
];
|
||||
|
||||
#onTypeChange(event: UUISelectEvent) {
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
// HACK: Workaround consolidating the old content-picker and dynamic-root. [LK:2024-01-24]
|
||||
if (this.nodeId && !this.dynamicRoot) {
|
||||
this.dynamicRoot = { originAlias: 'ByKey', originKey: this.nodeId, querySteps: [] };
|
||||
}
|
||||
}
|
||||
|
||||
#onContentTypeChange(event: UUISelectEvent) {
|
||||
event.stopPropagation();
|
||||
|
||||
this.type = event.target.value as UmbTreePickerSource['type'];
|
||||
this.type = event.target.value as UmbTreePickerSourceType;
|
||||
|
||||
this.nodeId = '';
|
||||
this.nodeId = undefined;
|
||||
this.dynamicRoot = undefined;
|
||||
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
}
|
||||
|
||||
#onIdChange(event: CustomEvent) {
|
||||
#onDocumentRootChange(event: CustomEvent) {
|
||||
switch (this.type) {
|
||||
case 'content':
|
||||
this.nodeId = (<UmbInputDocumentPickerRootElement>event.target).unique;
|
||||
this.dynamicRoot = (event.target as UmbInputDocumentPickerRootElement).data;
|
||||
|
||||
// HACK: Workaround consolidating the old content-picker and dynamic-root. [LK:2024-01-24]
|
||||
if (this.dynamicRoot?.originAlias === 'ByKey') {
|
||||
if (!this.dynamicRoot.querySteps || this.dynamicRoot.querySteps?.length === 0) {
|
||||
this.nodeId = this.dynamicRoot.originKey;
|
||||
} else {
|
||||
this.nodeId = undefined;
|
||||
}
|
||||
} else if (this.nodeId) {
|
||||
this.nodeId = undefined;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'media':
|
||||
case 'member':
|
||||
@@ -85,20 +109,20 @@ export class UmbInputTreePickerSourceElement extends FormControlMixin(UmbLitElem
|
||||
break;
|
||||
}
|
||||
|
||||
this.dispatchEvent(new CustomEvent(event.type));
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<umb-input-dropdown-list
|
||||
.options=${this._options}
|
||||
@change="${this.#onTypeChange}"></umb-input-dropdown-list>
|
||||
${this.#renderType()}`;
|
||||
@change="${this.#onContentTypeChange}"
|
||||
.options=${this._options}></umb-input-dropdown-list>
|
||||
${this.#renderSourcePicker()}`;
|
||||
}
|
||||
|
||||
#renderType() {
|
||||
#renderSourcePicker() {
|
||||
switch (this.type) {
|
||||
case 'content':
|
||||
return this.#renderTypeContent();
|
||||
return this.#renderDocumentSourcePicker();
|
||||
case 'media':
|
||||
case 'member':
|
||||
default:
|
||||
@@ -106,10 +130,10 @@ export class UmbInputTreePickerSourceElement extends FormControlMixin(UmbLitElem
|
||||
}
|
||||
}
|
||||
|
||||
#renderTypeContent() {
|
||||
#renderDocumentSourcePicker() {
|
||||
return html`<umb-input-document-picker-root
|
||||
@change=${this.#onIdChange}
|
||||
.nodeId=${this.nodeId}></umb-input-document-picker-root>`;
|
||||
@change=${this.#onDocumentRootChange}
|
||||
.data=${this.dynamicRoot}></umb-input-document-picker-root>`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { ManifestBase } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
export interface ManifestDynamicRootOrigin extends ManifestBase {
|
||||
type: 'dynamicRootOrigin';
|
||||
meta: MetaDynamicRootOrigin;
|
||||
}
|
||||
|
||||
export interface ManifestDynamicRootQueryStep extends ManifestBase {
|
||||
type: 'dynamicRootQueryStep';
|
||||
meta: MetaDynamicRootQueryStep;
|
||||
}
|
||||
|
||||
export interface MetaDynamicRootOrigin {
|
||||
originAlias: string;
|
||||
label?: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export interface MetaDynamicRootQueryStep {
|
||||
queryStepAlias: string;
|
||||
label?: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import type { ManifestCollection } from './collection.models.js';
|
||||
import type { ManifestCollectionView } from './collection-view.model.js';
|
||||
import type { ManifestDashboard } from './dashboard.model.js';
|
||||
import type { ManifestDashboardCollection } from './dashboard-collection.model.js';
|
||||
import type { ManifestDynamicRootOrigin, ManifestDynamicRootQueryStep } from './dynamic-root.model.js';
|
||||
import type { ManifestEntityAction } from './entity-action.model.js';
|
||||
import type { ManifestEntityBulkAction } from './entity-bulk-action.model.js';
|
||||
import type { ManifestExternalLoginProvider } from './external-login-provider.model.js';
|
||||
@@ -47,6 +48,7 @@ export type * from './collection-action.model.js';
|
||||
export type * from './collection-view.model.js';
|
||||
export type * from './dashboard-collection.model.js';
|
||||
export type * from './dashboard.model.js';
|
||||
export type * from './dynamic-root.model.js';
|
||||
export type * from './entity-action.model.js';
|
||||
export type * from './entity-bulk-action.model.js';
|
||||
export type * from './external-login-provider.model.js';
|
||||
@@ -87,6 +89,8 @@ export type ManifestTypes =
|
||||
| ManifestCollectionAction
|
||||
| ManifestDashboard
|
||||
| ManifestDashboardCollection
|
||||
| ManifestDynamicRootOrigin
|
||||
| ManifestDynamicRootQueryStep
|
||||
| ManifestEntityAction
|
||||
| ManifestEntityBulkAction
|
||||
| ManifestEntryPoint
|
||||
|
||||
@@ -37,8 +37,9 @@ export class UmbPropertyEditorUITreePickerSourcePickerElement
|
||||
render() {
|
||||
return html`<umb-input-tree-picker-source
|
||||
@change=${this.#onChange}
|
||||
.type=${this.value?.type}
|
||||
.nodeId=${this.value?.id}></umb-input-tree-picker-source>`;
|
||||
.type=${this.value?.type ?? 'content'}
|
||||
.nodeId=${this.value?.id}
|
||||
.dynamicRoot=${this.value?.dynamicRoot}></umb-input-tree-picker-source>`;
|
||||
}
|
||||
|
||||
static styles = [UmbTextStyles];
|
||||
|
||||
@@ -50,8 +50,10 @@ export class UmbPropertyEditorUITreePickerSourceTypePickerElement
|
||||
this.observe(
|
||||
await this.#datasetContext.propertyValueByAlias('startNode'),
|
||||
(value) => {
|
||||
if (!value) return;
|
||||
|
||||
const startNode = value as UmbTreePickerSource;
|
||||
if (startNode.type) {
|
||||
if (startNode?.type) {
|
||||
// If we had a sourceType before, we can see this as a change and not the initial value,
|
||||
// so let's reset the value, so we don't carry over content-types to the new source type.
|
||||
if (this.#initialized && this.sourceType !== startNode.type) {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { type UmbPropertyEditorConfigCollection, UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor';
|
||||
import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor';
|
||||
import { UmbDynamicRootRepository } from '@umbraco-cms/backoffice/dynamic-root';
|
||||
import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
|
||||
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
|
||||
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { UmbInputTreeElement } from '@umbraco-cms/backoffice/tree';
|
||||
import type { UmbTreePickerSource } from '@umbraco-cms/backoffice/components';
|
||||
@@ -16,7 +19,7 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen
|
||||
value = '';
|
||||
|
||||
@state()
|
||||
type?: UmbTreePickerSource['type'];
|
||||
type: UmbTreePickerSource['type'] = 'content';
|
||||
|
||||
@state()
|
||||
startNodeId?: string | null;
|
||||
@@ -36,12 +39,19 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen
|
||||
@state()
|
||||
ignoreUserStartNodes?: boolean;
|
||||
|
||||
#dynamicRoot?: UmbTreePickerSource['dynamicRoot'];
|
||||
|
||||
#dynamicRootRepository = new UmbDynamicRootRepository(this);
|
||||
|
||||
#workspaceContext?: typeof UMB_WORKSPACE_CONTEXT.TYPE;
|
||||
|
||||
@property({ attribute: false })
|
||||
public set config(config: UmbPropertyEditorConfigCollection | undefined) {
|
||||
const startNode: UmbTreePickerSource | undefined = config?.getValueByAlias('startNode');
|
||||
if (startNode) {
|
||||
this.type = startNode.type;
|
||||
this.startNodeId = startNode.id;
|
||||
this.#dynamicRoot = startNode.dynamicRoot;
|
||||
}
|
||||
|
||||
this.min = Number(config?.getValueByAlias('minNumber')) || 0;
|
||||
@@ -52,6 +62,34 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen
|
||||
this.ignoreUserStartNodes = config?.getValueByAlias('ignoreUserStartNodes');
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeContext(UMB_WORKSPACE_CONTEXT, (workspaceContext) => {
|
||||
this.#workspaceContext = workspaceContext;
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
this.#setStartNodeId();
|
||||
}
|
||||
|
||||
async #setStartNodeId() {
|
||||
if (this.startNodeId) return;
|
||||
|
||||
const entityId = this.#workspaceContext?.getEntityId();
|
||||
// TODO: Awaiting the workspace context to have a parent entity ID value. [LK]
|
||||
// e.g. const parentEntityId = this.#workspaceContext?.getParentEntityId();
|
||||
if (entityId && this.#dynamicRoot) {
|
||||
const result = await this.#dynamicRootRepository.postDynamicRootQuery(this.#dynamicRoot, entityId);
|
||||
if (result && result.length > 0) {
|
||||
this.startNodeId = result[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#onChange(e: CustomEvent) {
|
||||
this.value = (e.target as UmbInputTreeElement).value as string;
|
||||
this.dispatchEvent(new UmbPropertyValueChangeEvent());
|
||||
|
||||
@@ -13,7 +13,7 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _type: UmbTreePickerSource['type'] = undefined;
|
||||
private _type: UmbTreePickerSource['type'] = 'content';
|
||||
@property()
|
||||
public set type(newType: UmbTreePickerSource['type']) {
|
||||
const oldType = this._type;
|
||||
|
||||
@@ -1,101 +1,260 @@
|
||||
import { UmbDocumentPickerContext } from '../input-document/input-document.context.js';
|
||||
import type { UmbDocumentItemModel } from '../../repository/index.js';
|
||||
import { html, customElement, property, state, ifDefined, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { html, css, customElement, property, ifDefined, state, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbId } from '@umbraco-cms/backoffice/id';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import type { DocumentItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import {
|
||||
UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL,
|
||||
UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL,
|
||||
} from '@umbraco-cms/backoffice/dynamic-root';
|
||||
import type {
|
||||
ManifestDynamicRootOrigin,
|
||||
ManifestDynamicRootQueryStep,
|
||||
} from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { UmbModalContext } from '@umbraco-cms/backoffice/modal';
|
||||
import type { UmbTreePickerDynamicRoot, UmbTreePickerDynamicRootQueryStep } from '@umbraco-cms/backoffice/components';
|
||||
|
||||
@customElement('umb-input-document-picker-root')
|
||||
export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitElement) {
|
||||
public get unique(): string | null | undefined {
|
||||
return this.#documentPickerContext.getSelection()[0];
|
||||
}
|
||||
public set unique(unique: string | null | undefined) {
|
||||
const selection = unique ? [unique] : [];
|
||||
this.#documentPickerContext.setSelection(selection);
|
||||
}
|
||||
|
||||
@property()
|
||||
public set value(unique: string) {
|
||||
this.unique = unique;
|
||||
}
|
||||
|
||||
@state()
|
||||
private _items?: Array<UmbDocumentItemModel>;
|
||||
|
||||
#documentPickerContext = new UmbDocumentPickerContext(this);
|
||||
|
||||
// TODO: DynamicRoot - once feature implemented, wire up context and picker UI. [LK]
|
||||
#dynamicRootPickerContext = {
|
||||
openPicker: () => {
|
||||
throw new Error('DynamicRoot picker has not been implemented yet.');
|
||||
},
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.#documentPickerContext.max = 1;
|
||||
|
||||
this.observe(this.#documentPickerContext.selection, (selection) => (super.value = selection.join(',')));
|
||||
this.observe(this.#documentPickerContext.selectedItems, (selectedItems) => (this._items = selectedItems));
|
||||
}
|
||||
|
||||
protected getFormElement() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@state()
|
||||
private _originManifests: Array<ManifestDynamicRootOrigin> = [];
|
||||
|
||||
@state()
|
||||
private _queryStepManifests: Array<ManifestDynamicRootQueryStep> = [];
|
||||
|
||||
@property({ attribute: false })
|
||||
data?: UmbTreePickerDynamicRoot | undefined;
|
||||
|
||||
#dynamicRootOrigin?: { label: string; icon: string; description?: string };
|
||||
|
||||
#modalContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE;
|
||||
|
||||
#openModal?: UmbModalContext;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this.#modalContext = instance;
|
||||
});
|
||||
|
||||
this.observe(
|
||||
umbExtensionsRegistry.byType('dynamicRootOrigin'),
|
||||
(originManifests: Array<ManifestDynamicRootOrigin>) => {
|
||||
this._originManifests = originManifests;
|
||||
},
|
||||
);
|
||||
|
||||
this.observe(
|
||||
umbExtensionsRegistry.byType('dynamicRootQueryStep'),
|
||||
(queryStepManifests: Array<ManifestDynamicRootQueryStep>) => {
|
||||
this._queryStepManifests = queryStepManifests;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
this.#updateDynamicRootOrigin(this.data);
|
||||
this.#updateDynamicRootQuerySteps(this.data?.querySteps);
|
||||
}
|
||||
|
||||
#sorter = new UmbSorterController<UmbTreePickerDynamicRootQueryStep>(this, {
|
||||
getUniqueOfElement: (element) => {
|
||||
return element.id;
|
||||
},
|
||||
getUniqueOfModel: (modelEntry) => {
|
||||
return modelEntry.unique;
|
||||
},
|
||||
identifier: 'Umb.SorterIdentifier.InputDocumentPickerRoot',
|
||||
itemSelector: 'uui-ref-node',
|
||||
containerSelector: '#query-steps',
|
||||
onChange: ({ model }) => {
|
||||
if (this.data && this.data.querySteps) {
|
||||
const querySteps = model;
|
||||
this.#updateDynamicRootQuerySteps(querySteps);
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
#openDynamicRootOriginPicker() {
|
||||
this.#openModal = this.#modalContext?.open(UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL, {});
|
||||
this.#openModal?.onSubmit().then((data: UmbTreePickerDynamicRoot) => {
|
||||
const existingData = { ...this.data };
|
||||
existingData.originKey = undefined;
|
||||
this.data = { ...existingData, ...data };
|
||||
this.#updateDynamicRootOrigin(this.data);
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
});
|
||||
}
|
||||
|
||||
#openDynamicRootQueryStepPicker() {
|
||||
this.#openModal = this.#modalContext?.open(UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL, {});
|
||||
this.#openModal?.onSubmit().then((step) => {
|
||||
if (this.data) {
|
||||
const querySteps = [...(this.data.querySteps ?? []), step];
|
||||
this.#updateDynamicRootQuerySteps(querySteps);
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#updateDynamicRootOrigin(data?: UmbTreePickerDynamicRoot) {
|
||||
if (!data) return;
|
||||
const origin = this._originManifests.find((item) => item.meta.originAlias === data.originAlias)?.meta;
|
||||
this.#dynamicRootOrigin = {
|
||||
label: origin?.label ?? data.originAlias,
|
||||
icon: origin?.icon ?? 'icon-wand',
|
||||
description: data.originKey,
|
||||
};
|
||||
}
|
||||
|
||||
#updateDynamicRootQuerySteps(querySteps?: Array<UmbTreePickerDynamicRootQueryStep>) {
|
||||
if (!this.data) return;
|
||||
|
||||
if (querySteps) {
|
||||
// NOTE: Ensure that the `unique` ID is populated for each query step. [LK]
|
||||
querySteps = querySteps.map((item) => (item.unique ? item : { ...item, unique: UmbId.new() }));
|
||||
}
|
||||
|
||||
this.#sorter?.setModel(querySteps ?? []);
|
||||
this.data = { ...this.data, ...{ querySteps } };
|
||||
}
|
||||
|
||||
#getQueryStepMeta(item: UmbTreePickerDynamicRootQueryStep): {
|
||||
unique: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
description?: string;
|
||||
} {
|
||||
const step = this._queryStepManifests.find((step) => step.meta.queryStepAlias === item.alias)?.meta;
|
||||
const docTypes = item.anyOfDocTypeKeys?.join(', ');
|
||||
const description = docTypes ? this.localize.term('dynamicRoot_queryStepTypes') + docTypes : undefined;
|
||||
|
||||
return {
|
||||
unique: item.unique,
|
||||
label: step?.label ?? item.alias,
|
||||
icon: step?.icon ?? 'icon-lab',
|
||||
description,
|
||||
};
|
||||
}
|
||||
|
||||
#removeDynamicRootQueryStep(item: UmbTreePickerDynamicRootQueryStep) {
|
||||
if (this.data?.querySteps) {
|
||||
const index = this.data.querySteps.indexOf(item);
|
||||
if (index !== -1) {
|
||||
const querySteps = [...this.data.querySteps];
|
||||
querySteps.splice(index, 1);
|
||||
this.#updateDynamicRootQuerySteps(querySteps);
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#clearDynamicRootQuery() {
|
||||
this.data = undefined;
|
||||
this.#dynamicRootOrigin = undefined;
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${this._items
|
||||
? html` <uui-ref-list
|
||||
>${repeat(
|
||||
this._items,
|
||||
(item) => item.unique,
|
||||
(item) => this._renderItem(item),
|
||||
)}
|
||||
</uui-ref-list>`
|
||||
: ''}
|
||||
${this.#renderButtons()}
|
||||
${this.#renderAddOriginButton()}
|
||||
<uui-ref-list>${this.#renderOrigin()}</uui-ref-list>
|
||||
<uui-ref-list id="query-steps">${this.#renderQuerySteps()}</uui-ref-list>
|
||||
${this.#renderAddQueryStepButton()} ${this.#renderClearButton()}
|
||||
`;
|
||||
}
|
||||
|
||||
#renderButtons() {
|
||||
if (this.unique) return;
|
||||
|
||||
//TODO: Dynamic paths
|
||||
return html` <uui-button-group>
|
||||
#renderAddOriginButton() {
|
||||
if (this.data?.originAlias) return;
|
||||
return html`
|
||||
<uui-button
|
||||
look="placeholder"
|
||||
@click=${() => this.#documentPickerContext.openPicker()}
|
||||
label=${this.localize.term('contentPicker_defineRootNode')}></uui-button>
|
||||
<uui-button
|
||||
look="placeholder"
|
||||
@click=${() => this.#dynamicRootPickerContext.openPicker()}
|
||||
label=${this.localize.term('contentPicker_defineDynamicRoot')}></uui-button>
|
||||
</uui-button-group>`;
|
||||
class="add-button"
|
||||
@click=${this.#openDynamicRootOriginPicker}
|
||||
label=${this.localize.term('contentPicker_defineDynamicRoot')}
|
||||
look="placeholder"></uui-button>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderItem(item: UmbDocumentItemModel) {
|
||||
if (!item.unique) return;
|
||||
// TODO: get correct variant name
|
||||
const name = item.variants[0]?.name;
|
||||
#renderOrigin() {
|
||||
if (!this.#dynamicRootOrigin) return;
|
||||
return html`
|
||||
<uui-ref-node name=${name} detail=${ifDefined(item.unique)}>
|
||||
<!-- TODO: implement is trashed <uui-tag size="s" slot="tag" color="danger">Trashed</uui-tag> -->
|
||||
<uui-ref-node
|
||||
border
|
||||
standalone
|
||||
name=${this.#dynamicRootOrigin.label}
|
||||
detail=${ifDefined(this.#dynamicRootOrigin.description)}>
|
||||
<uui-icon slot="icon" name=${ifDefined(this.#dynamicRootOrigin.icon)}></uui-icon>
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button @click=${() => this.#documentPickerContext.openPicker()} label="Edit document ${name}"
|
||||
>Edit</uui-button
|
||||
>
|
||||
<uui-button
|
||||
@click=${() => this.#documentPickerContext.requestRemoveItem(item.unique)}
|
||||
label="Remove document ${name}"
|
||||
>Remove</uui-button
|
||||
>
|
||||
@click=${this.#openDynamicRootOriginPicker}
|
||||
label="${this.localize.term('general_edit')}"></uui-button>
|
||||
</uui-action-bar>
|
||||
</uui-ref-node>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderClearButton() {
|
||||
if (!this.#dynamicRootOrigin) return;
|
||||
return html`
|
||||
<uui-button @click=${this.#clearDynamicRootQuery}>${this.localize.term('buttons_clearSelection')}</uui-button>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderQuerySteps() {
|
||||
if (!this.data?.querySteps) return;
|
||||
return repeat(
|
||||
this.data.querySteps,
|
||||
(item) => item.unique,
|
||||
(item) => this.#renderQueryStep(item),
|
||||
);
|
||||
}
|
||||
|
||||
#renderQueryStep(item: UmbTreePickerDynamicRootQueryStep) {
|
||||
if (!item.alias) return;
|
||||
const step = this.#getQueryStepMeta(item);
|
||||
return html`
|
||||
<uui-ref-node border standalone id=${step.unique} name=${step.label} detail="${ifDefined(step.description)}">
|
||||
<uui-icon slot="icon" name=${step.icon}></uui-icon>
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button
|
||||
@click=${() => this.#removeDynamicRootQueryStep(item)}
|
||||
label=${this.localize.term('general_remove')}></uui-button>
|
||||
</uui-action-bar>
|
||||
</uui-ref-node>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderAddQueryStepButton() {
|
||||
if (!this.#dynamicRootOrigin) return;
|
||||
return html` <uui-button
|
||||
class="add-button"
|
||||
@click=${this.#openDynamicRootQueryStepPicker}
|
||||
label=${this.localize.term('dynamicRoot_addQueryStep')}
|
||||
look="placeholder"></uui-button>`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
.add-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
uui-ref-node[drag-placeholder] {
|
||||
opacity: 0.2;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default UmbInputDocumentPickerRootElement;
|
||||
|
||||
@@ -121,6 +121,13 @@ export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) {
|
||||
this._editDocumentPath = routeBuilder({});
|
||||
});
|
||||
|
||||
this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(',')));
|
||||
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems));
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
this.addValidator(
|
||||
'rangeUnderflow',
|
||||
() => this.minMessage,
|
||||
@@ -132,9 +139,6 @@ export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) {
|
||||
() => this.maxMessage,
|
||||
() => !!this.max && this.#pickerContext.getSelection().length > this.max,
|
||||
);
|
||||
|
||||
this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(',')));
|
||||
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems));
|
||||
}
|
||||
|
||||
protected getFormElement() {
|
||||
@@ -150,7 +154,7 @@ export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) {
|
||||
|
||||
#openPicker() {
|
||||
// TODO: Configure the content picker, with `startNodeId` and `ignoreUserStartNodes` [LK]
|
||||
console.log('_openPicker', [this.startNodeId, this.ignoreUserStartNodes]);
|
||||
console.log('#openPicker', [this.startNodeId, this.ignoreUserStartNodes]);
|
||||
this.#pickerContext.openPicker({
|
||||
hideTreeRoot: true,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './modals/index.js';
|
||||
export * from './repository/index.js';
|
||||
@@ -0,0 +1,7 @@
|
||||
import { manifests as modalManifests } from './modals/manifests.js';
|
||||
import { manifests as repositoryManifests } from './repository/manifests.js';
|
||||
|
||||
export const manifests = [
|
||||
...modalManifests,
|
||||
...repositoryManifests,
|
||||
];
|
||||
@@ -0,0 +1,109 @@
|
||||
import { UmbDocumentPickerContext } from '../../documents/documents/components/input-document/input-document.context.js';
|
||||
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
|
||||
import { css, html, customElement, map, state, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { ManifestDynamicRootOrigin } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { UmbTreePickerDynamicRoot } from '@umbraco-cms/backoffice/components';
|
||||
|
||||
@customElement('umb-dynamic-root-origin-picker-modal')
|
||||
export class UmbDynamicRootOriginPickerModalModalElement extends UmbModalBaseElement {
|
||||
@state()
|
||||
private _origins: Array<ManifestDynamicRootOrigin> = [];
|
||||
|
||||
#documentPickerContext = new UmbDocumentPickerContext(this);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.#documentPickerContext.max = 1;
|
||||
|
||||
this.observe(umbExtensionsRegistry.byType('dynamicRootOrigin'), (origins: Array<ManifestDynamicRootOrigin>) => {
|
||||
this._origins = origins;
|
||||
});
|
||||
}
|
||||
|
||||
#choose(item: ManifestDynamicRootOrigin) {
|
||||
switch (item.meta.originAlias) {
|
||||
// NOTE: Edge-case. Currently this is the only one that uses a document picker,
|
||||
// but other custom origins may want other configuration options. [LK:2024-01-25]
|
||||
case 'ByKey':
|
||||
this.#openDocumentPicker(item.meta.originAlias);
|
||||
break;
|
||||
default:
|
||||
this.#submit({ originAlias: item.meta.originAlias });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#close() {
|
||||
this.modalContext?.reject();
|
||||
}
|
||||
|
||||
async #openDocumentPicker(originAlias: string) {
|
||||
await this.#documentPickerContext.openPicker({
|
||||
hideTreeRoot: true,
|
||||
});
|
||||
|
||||
const selectedItems = this.#documentPickerContext.getSelection();
|
||||
if (selectedItems.length !== 1) return;
|
||||
|
||||
this.#submit({
|
||||
originAlias,
|
||||
originKey: selectedItems[0],
|
||||
});
|
||||
}
|
||||
|
||||
#submit(value: UmbTreePickerDynamicRoot) {
|
||||
this.modalContext?.setValue(value);
|
||||
this.modalContext?.submit();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-body-layout headline="${this.localize.term('dynamicRoot_pickDynamicRootOriginTitle')}">
|
||||
<div id="main">
|
||||
<uui-box>
|
||||
${map(
|
||||
this._origins,
|
||||
(item) => html`
|
||||
<uui-button @click=${() => this.#choose(item)} look="placeholder" label="${ifDefined(item.meta.label)}">
|
||||
<h3>${item.meta.label}</h3>
|
||||
<p>${item.meta.description}</p>
|
||||
</uui-button>
|
||||
`,
|
||||
)}
|
||||
</uui-box>
|
||||
</div>
|
||||
<div slot="actions">
|
||||
<uui-button @click=${this.#close} look="default" label="${this.localize.term('general_close')}"></uui-button>
|
||||
</div>
|
||||
</umb-body-layout>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
uui-box > uui-button {
|
||||
display: block;
|
||||
--uui-button-content-align: flex-start;
|
||||
}
|
||||
|
||||
uui-box > uui-button:not(:last-of-type) {
|
||||
margin-bottom: var(--uui-size-space-5);
|
||||
}
|
||||
|
||||
h3,
|
||||
p {
|
||||
text-align: left;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default UmbDynamicRootOriginPickerModalModalElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-dynamic-root-origin-picker-modal': UmbDynamicRootOriginPickerModalModalElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import { UmbDocumentTypePickerContext } from '../../documents/document-types/components/input-document-type/input-document-type.context.js';
|
||||
import { UmbId } from '@umbraco-cms/backoffice/id';
|
||||
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { css, html, customElement, map, state, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { UmbTreePickerDynamicRootQueryStep } from '@umbraco-cms/backoffice/components';
|
||||
import type { ManifestDynamicRootQueryStep } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
@customElement('umb-dynamic-root-query-step-picker-modal')
|
||||
export class UmbDynamicRootQueryStepPickerModalModalElement extends UmbModalBaseElement {
|
||||
@state()
|
||||
private _querySteps: Array<ManifestDynamicRootQueryStep> = [];
|
||||
|
||||
#documentTypePickerContext = new UmbDocumentTypePickerContext(this);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.observe(
|
||||
umbExtensionsRegistry.byType('dynamicRootQueryStep'),
|
||||
(querySteps: Array<ManifestDynamicRootQueryStep>) => {
|
||||
this._querySteps = querySteps;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#choose(item: ManifestDynamicRootQueryStep) {
|
||||
this.#openDocumentTypePicker(item.meta.queryStepAlias);
|
||||
}
|
||||
|
||||
#close() {
|
||||
this.modalContext?.reject();
|
||||
}
|
||||
|
||||
async #openDocumentTypePicker(alias: string) {
|
||||
await this.#documentTypePickerContext.openPicker({
|
||||
hideTreeRoot: true,
|
||||
pickableFilter: (x) => x.isElement === false,
|
||||
});
|
||||
|
||||
const selectedItems = this.#documentTypePickerContext.getSelection();
|
||||
|
||||
this.#submit({
|
||||
unique: UmbId.new(),
|
||||
alias: alias,
|
||||
anyOfDocTypeKeys: selectedItems,
|
||||
});
|
||||
}
|
||||
|
||||
#submit(value: UmbTreePickerDynamicRootQueryStep) {
|
||||
this.modalContext?.setValue(value);
|
||||
this.modalContext?.submit();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-body-layout headline="${this.localize.term('dynamicRoot_pickDynamicRootQueryStepTitle')}">
|
||||
<div id="main">
|
||||
<uui-box>
|
||||
${map(
|
||||
this._querySteps,
|
||||
(item) => html`
|
||||
<uui-button @click=${() => this.#choose(item)} look="placeholder" label="${ifDefined(item.meta.label)}">
|
||||
<h3>${item.meta.label}</h3>
|
||||
<p>${item.meta.description}</p>
|
||||
</uui-button>
|
||||
`,
|
||||
)}
|
||||
</uui-box>
|
||||
</div>
|
||||
<div slot="actions">
|
||||
<uui-button @click=${this.#close} look="default" label="${this.localize.term('general_close')}"></uui-button>
|
||||
</div>
|
||||
</umb-body-layout>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
UmbTextStyles,
|
||||
css`
|
||||
uui-box > uui-button {
|
||||
display: block;
|
||||
--uui-button-content-align: flex-start;
|
||||
}
|
||||
|
||||
uui-box > uui-button:not(:last-of-type) {
|
||||
margin-bottom: var(--uui-size-space-5);
|
||||
}
|
||||
|
||||
h3,
|
||||
p {
|
||||
text-align: left;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default UmbDynamicRootQueryStepPickerModalModalElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-dynamic-root-query-step-picker-modal': UmbDynamicRootQueryStepPickerModalModalElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import {
|
||||
UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL_ALIAS,
|
||||
UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL_ALIAS,
|
||||
} from './manifests.js';
|
||||
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
export const UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL = new UmbModalToken(UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL_ALIAS, {
|
||||
modal: {
|
||||
type: 'sidebar',
|
||||
size: 'small',
|
||||
},
|
||||
});
|
||||
|
||||
export const UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL = new UmbModalToken(
|
||||
UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL_ALIAS,
|
||||
{
|
||||
modal: {
|
||||
type: 'sidebar',
|
||||
size: 'small',
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,139 @@
|
||||
import type {
|
||||
ManifestDynamicRootOrigin,
|
||||
ManifestDynamicRootQueryStep,
|
||||
ManifestModal,
|
||||
} from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL_ALIAS = 'Umb.Modal.DynamicRoot.OriginPicker';
|
||||
export const UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL_ALIAS = 'Umb.Modal.DynamicRoot.QueryStepPicker';
|
||||
|
||||
const modals: Array<ManifestModal> = [
|
||||
{
|
||||
type: 'modal',
|
||||
alias: UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL_ALIAS,
|
||||
name: 'Choose an origin',
|
||||
js: () => import('./dynamic-root-origin-picker-modal.element.js'),
|
||||
},
|
||||
{
|
||||
type: 'modal',
|
||||
alias: UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL_ALIAS,
|
||||
name: 'Append step to query',
|
||||
js: () => import('./dynamic-root-query-step-picker-modal.element.js'),
|
||||
},
|
||||
];
|
||||
|
||||
const origins: Array<ManifestDynamicRootOrigin> = [
|
||||
{
|
||||
type: 'dynamicRootOrigin',
|
||||
alias: 'Umb.DynamicRootOrigin.Root',
|
||||
name: 'Dynamic Root Origin: Root',
|
||||
meta: {
|
||||
originAlias: 'Root',
|
||||
label: 'Root',
|
||||
description: 'Root node of this editing session.',
|
||||
icon: 'icon-home',
|
||||
},
|
||||
weight: 100,
|
||||
},
|
||||
{
|
||||
type: 'dynamicRootOrigin',
|
||||
alias: 'Umb.DynamicRootOrigin.Parent',
|
||||
name: 'Dynamic Root Origin: Parent',
|
||||
meta: {
|
||||
originAlias: 'Parent',
|
||||
label: 'Parent',
|
||||
description: 'The parent node of the source in this editing session.',
|
||||
icon: 'icon-page-up',
|
||||
},
|
||||
weight: 90,
|
||||
},
|
||||
{
|
||||
type: 'dynamicRootOrigin',
|
||||
alias: 'Umb.DynamicRootOrigin.Current',
|
||||
name: 'Dynamic Root Origin: Current',
|
||||
meta: {
|
||||
originAlias: 'Current',
|
||||
label: 'Current',
|
||||
description: 'The content node that is source for this editing session.',
|
||||
icon: 'icon-document',
|
||||
},
|
||||
weight: 80,
|
||||
},
|
||||
{
|
||||
type: 'dynamicRootOrigin',
|
||||
alias: 'Umb.DynamicRootOrigin.Site',
|
||||
name: 'Dynamic Root Origin: Site',
|
||||
meta: {
|
||||
originAlias: 'Site',
|
||||
label: 'Site',
|
||||
description: 'Find nearest node with a hostname.',
|
||||
icon: 'icon-home',
|
||||
},
|
||||
weight: 70,
|
||||
},
|
||||
{
|
||||
type: 'dynamicRootOrigin',
|
||||
alias: 'Umb.DynamicRootOrigin.ByKey',
|
||||
name: 'Dynamic Root Origin: By Key',
|
||||
meta: {
|
||||
originAlias: 'ByKey',
|
||||
label: 'Specific Node',
|
||||
description: 'Pick a specific Node as the origin for this query.',
|
||||
icon: 'icon-wand',
|
||||
},
|
||||
weight: 60,
|
||||
},
|
||||
];
|
||||
|
||||
const querySteps: Array<ManifestDynamicRootQueryStep> = [
|
||||
{
|
||||
type: 'dynamicRootQueryStep',
|
||||
alias: 'Umb.DynamicRootQueryStep.NearestAncestorOrSelf',
|
||||
name: 'Dynamic Root Query Step: Nearest Ancestor Or Self',
|
||||
meta: {
|
||||
queryStepAlias: 'NearestAncestorOrSelf',
|
||||
label: 'Nearest Ancestor Or Self',
|
||||
description: 'Query the nearest ancestor or self that fits with one of the configured types.',
|
||||
icon: 'icon-arrow-up',
|
||||
},
|
||||
weight: 100,
|
||||
},
|
||||
{
|
||||
type: 'dynamicRootQueryStep',
|
||||
alias: 'Umb.DynamicRootQueryStep.FurthestAncestorOrSelf',
|
||||
name: 'Dynamic Root Query Step: Furthest Ancestor Or Self',
|
||||
meta: {
|
||||
queryStepAlias: 'FurthestAncestorOrSelf',
|
||||
label: 'Furthest Ancestor Or Self',
|
||||
description: 'Query the furthest ancestor or self that fits with one of the configured types.',
|
||||
icon: 'icon-arrow-up',
|
||||
},
|
||||
weight: 90,
|
||||
},
|
||||
{
|
||||
type: 'dynamicRootQueryStep',
|
||||
alias: 'Umb.DynamicRootQueryStep.NearestDescendantOrSelf',
|
||||
name: 'Dynamic Root Query Step: Nearest Descendant Or Self',
|
||||
meta: {
|
||||
queryStepAlias: 'NearestDescendantOrSelf',
|
||||
label: 'Nearest Descendant Or Self',
|
||||
description: 'Query the nearest descendant or self that fits with one of the configured types.',
|
||||
icon: 'icon-arrow-down',
|
||||
},
|
||||
weight: 80,
|
||||
},
|
||||
{
|
||||
type: 'dynamicRootQueryStep',
|
||||
alias: 'Umb.DynamicRootQueryStep.FurthestDescendantOrSelf',
|
||||
name: 'Dynamic Root Query Step: Furthest Descendant Or Self',
|
||||
meta: {
|
||||
queryStepAlias: 'FurthestDescendantOrSelf',
|
||||
label: 'Furthest Descendant Or Self',
|
||||
description: 'Query the furthest descendant or self that fits with one of the configured types.',
|
||||
icon: 'icon-arrow-down',
|
||||
},
|
||||
weight: 70,
|
||||
},
|
||||
];
|
||||
|
||||
export const manifests = [...modals, ...origins, ...querySteps];
|
||||
@@ -0,0 +1,42 @@
|
||||
import { UmbDynamicRootServerDataSource } from './dynamic-root.server.data.js';
|
||||
import { UmbBaseController } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { DynamicRootRequestModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
import type { UmbTreePickerDynamicRoot } from '@umbraco-cms/backoffice/components';
|
||||
|
||||
const GUID_EMPTY: string = '00000000-0000-0000-0000-000000000000';
|
||||
|
||||
export class UmbDynamicRootRepository extends UmbBaseController {
|
||||
#dataSource: UmbDynamicRootServerDataSource;
|
||||
|
||||
constructor(host: UmbControllerHostElement) {
|
||||
super(host);
|
||||
|
||||
this.#dataSource = new UmbDynamicRootServerDataSource(host);
|
||||
}
|
||||
|
||||
async postDynamicRootQuery(query: UmbTreePickerDynamicRoot, entityId: string, parentId?: string) {
|
||||
const model: DynamicRootRequestModel = {
|
||||
context: {
|
||||
id: entityId,
|
||||
parentId: parentId ?? GUID_EMPTY,
|
||||
},
|
||||
query: {
|
||||
origin: {
|
||||
alias: query.originAlias,
|
||||
key: query.originKey,
|
||||
},
|
||||
steps: query.querySteps!.map((step) => {
|
||||
return {
|
||||
alias: step.alias!,
|
||||
documentTypeIds: step.anyOfDocTypeKeys!,
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
const result = await this.#dataSource.postDynamicRootQuery(model);
|
||||
|
||||
return result?.roots;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { DynamicRootResource } from '@umbraco-cms/backoffice/backend-api';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
|
||||
import type { DynamicRootRequestModel, DynamicRootResponseModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export class UmbDynamicRootServerDataSource {
|
||||
#host: UmbControllerHost;
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
this.#host = host;
|
||||
}
|
||||
|
||||
async postDynamicRootQuery(args: DynamicRootRequestModel): Promise<DynamicRootResponseModel | undefined> {
|
||||
if (!args.context) throw new Error('Dynamic Root context is missing');
|
||||
if (!args.query) throw new Error('Dynamic Root query is missing');
|
||||
|
||||
const requestBody: DynamicRootRequestModel = {
|
||||
context: args.context,
|
||||
query: args.query,
|
||||
};
|
||||
|
||||
const { data } = await tryExecuteAndNotify(this.#host, DynamicRootResource.postDynamicRootQuery({ requestBody }));
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { UMB_DYNAMIC_ROOT_REPOSITORY_ALIAS } from './manifests.js';
|
||||
export { UmbDynamicRootRepository } from './dynamic-root.repository.js';
|
||||
@@ -0,0 +1,3 @@
|
||||
export const UMB_DYNAMIC_ROOT_REPOSITORY_ALIAS = 'Umb.Repository.DynamicRoot';
|
||||
|
||||
export const manifests = [];
|
||||
@@ -0,0 +1,9 @@
|
||||
export const name = 'Umbraco.Core.DynamicRoot';
|
||||
export const extensions = [
|
||||
{
|
||||
name: 'Dynamic Root Bundle',
|
||||
alias: 'Umb.Bundle.DynamicRoot',
|
||||
type: 'bundle',
|
||||
js: () => import('./manifests.js'),
|
||||
},
|
||||
];
|
||||
@@ -92,6 +92,7 @@
|
||||
|
||||
"@umbraco-cms/backoffice/data-type": ["./src/packages/core/data-type/index.ts"],
|
||||
"@umbraco-cms/backoffice/language": ["src/packages/language/index.ts"],
|
||||
"@umbraco-cms/backoffice/dynamic-root": ["./src/packages/dynamic-root/index.ts"],
|
||||
"@umbraco-cms/backoffice/logviewer": ["src/packages/log-viewer/index.ts"],
|
||||
"@umbraco-cms/backoffice/relation-types": ["src/packages/relations/relation-types/index.ts"],
|
||||
"@umbraco-cms/backoffice/relations": ["src/packages/relations/relations/index.ts"],
|
||||
|
||||
@@ -108,6 +108,7 @@ export default {
|
||||
'@umbraco-cms/backoffice/language': './src/packages/language/index.ts',
|
||||
|
||||
'@umbraco-cms/backoffice/data-type': './src/packages/core/data-type/index.ts',
|
||||
'@umbraco-cms/backoffice/dynamic-root': './src/packages/dynamic-root/index.ts',
|
||||
'@umbraco-cms/backoffice/logviewer': './src/packages/settings/logviewer/index.ts',
|
||||
'@umbraco-cms/backoffice/relation-type': './src/packages/relations/relation-types/index.ts',
|
||||
'@umbraco-cms/backoffice/relation': './src/packages/relations/relations/index.ts',
|
||||
|
||||
Reference in New Issue
Block a user