Merge pull request #1164 from umbraco/feature/dynamic-root
Feature: Dynamic Root
This commit is contained in:
@@ -61,6 +61,7 @@
|
||||
"./member-type": "./dist-cms/packages/members/member-types/index.js",
|
||||
"./package": "./dist-cms/packages/packages/package/index.js",
|
||||
"./data-type": "./dist-cms/packages/core/data-type/index.js",
|
||||
"./dynamic-root": "./dist-cms/packages/dynamic-root/index.js",
|
||||
"./language": "./dist-cms/packages/settings/languages/index.js",
|
||||
"./logviewer": "./dist-cms/packages/settings/logviewer/index.js",
|
||||
"./relation-type": "./dist-cms/packages/relations/relation-types/index.js",
|
||||
|
||||
@@ -30,6 +30,7 @@ const CORE_PACKAGES = [
|
||||
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',
|
||||
|
||||
@@ -36,6 +36,7 @@ import { handlers as partialViewHandlers } from './handlers/partial-view/index.j
|
||||
import { handlers as tagHandlers } from './handlers/tag-handlers.js';
|
||||
import { handlers as configHandlers } from './handlers/config.handlers.js';
|
||||
import { handlers as scriptHandlers } from './handlers/script/index.js';
|
||||
import { handlers as dynamicRootHandlers } from './handlers/dynamic-root.handlers.js';
|
||||
|
||||
const handlers = [
|
||||
serverHandlers.serverInformationHandler,
|
||||
@@ -46,6 +47,7 @@ const handlers = [
|
||||
...dictionaryHandlers,
|
||||
...documentHandlers,
|
||||
...documentTypeHandlers,
|
||||
...dynamicRootHandlers,
|
||||
...examineManagementHandlers,
|
||||
...healthCheckHandlers,
|
||||
...installHandlers,
|
||||
|
||||
@@ -227,6 +227,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));
|
||||
}),
|
||||
];
|
||||
@@ -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'),
|
||||
},
|
||||
];
|
||||
@@ -91,6 +91,7 @@
|
||||
"@umbraco-cms/backoffice/temporary-file": ["src/packages/core/temporary-file"],
|
||||
|
||||
"@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/language": ["./src/packages/settings/languages/index.ts"],
|
||||
"@umbraco-cms/backoffice/logviewer": ["src/packages/log-viewer/index.ts"],
|
||||
"@umbraco-cms/backoffice/relation-types": ["src/packages/relations/relation-types/index.ts"],
|
||||
|
||||
@@ -106,6 +106,7 @@ export default {
|
||||
'@umbraco-cms/backoffice/package': './src/packages/packages/package/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/language': './src/packages/settings/languages/index.ts',
|
||||
'@umbraco-cms/backoffice/logviewer': './src/packages/settings/logviewer/index.ts',
|
||||
'@umbraco-cms/backoffice/relation-type': './src/packages/relations/relation-types/index.ts',
|
||||
|
||||
Reference in New Issue
Block a user