diff --git a/src/Umbraco.Web.UI.Client/.github/release.yml b/src/Umbraco.Web.UI.Client/.github/release.yml new file mode 100644 index 0000000000..0e5fa64bd3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/.github/release.yml @@ -0,0 +1,40 @@ +# .github/release.yml + +changelog: + exclude: + labels: + - ignore-for-release + - duplicate + - wontfix + categories: + - title: 🙌 Notable Changes + labels: + - notable + - title: 💥 Breaking Changes + labels: + - category/breaking + - title: 🚀 New Features + labels: + - type/feature + - category/feature + - type/enhancement + - category/enhancement + - title: 🐛 Bug Fixes + labels: + - type/bug + - category/bug + - title: 📄 Documentation + labels: + - documentation + - title: 🏠 Internal + labels: + - internal + - title: 📦 Dependencies + labels: + - dependencies + - title: 🌈 A11Y + labels: + - accessibility + - title: Other Changes + labels: + - '*' diff --git a/src/Umbraco.Web.UI.Client/examples/workspace-context-counter/counter-workspace-view.ts b/src/Umbraco.Web.UI.Client/examples/workspace-context-counter/counter-workspace-view.ts index 2b86fdcead..581a9fab53 100644 --- a/src/Umbraco.Web.UI.Client/examples/workspace-context-counter/counter-workspace-view.ts +++ b/src/Umbraco.Web.UI.Client/examples/workspace-context-counter/counter-workspace-view.ts @@ -1,5 +1,4 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { UMB_CURRENT_USER_CONTEXT } from '@umbraco-cms/backoffice/current-user'; import { css, html, customElement, state, LitElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; import { EXAMPLE_COUNTER_CONTEXT } from './counter-workspace-context'; diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index f70b3b950e..7df99088de 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -1,12 +1,12 @@ { "name": "@umbraco-cms/backoffice", - "version": "14.0.0-beta001", + "version": "14.0.0-beta002", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@umbraco-cms/backoffice", - "version": "14.0.0-beta001", + "version": "14.0.0-beta002", "license": "MIT", "dependencies": { "@openid/appauth": "^1.3.1", @@ -12235,9 +12235,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index cee2260680..9c765766c2 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -1,7 +1,7 @@ { "name": "@umbraco-cms/backoffice", "license": "MIT", - "version": "14.0.0-beta001", + "version": "14.0.0-beta002", "type": "module", "exports": { ".": null, diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts index 01f5e94555..d802dcc5cd 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts @@ -308,7 +308,7 @@ export default { languagesToSendForApproval: 'Hvilke sprog vil du gerne sende til godkendelse?', languagesToSchedule: 'Hvilke sprog vil du gerne planlægge?', languagesToUnpublish: - 'Vælg sproget du vil afpublicere. Afpublicering af et obligatorisk sprog vil\n afpublicere alle sprog.\n ', + 'Vælg sproget du vil afpublicere. Afpublicering af et obligatorisk sprog vil afpublicere alle sprog.', resetFocalPoint: 'Nulstil fokuspunkt', variantsWillBeSaved: 'Alle nye varianter vil blive gemt.', publishRequiresVariants: 'De følgende varianter er krævet for at en udgivelse kan finde sted:', @@ -2124,6 +2124,10 @@ export default { 'This item or its descendants is being referenced. Unpublishing can lead to broken links on your website. Please take the appropriate actions.', deleteDisabledWarning: 'This item or its descendants is being referenced. Therefore, deletion has been disabled.', listViewDialogWarning: 'The following items you are trying to %0% are referenced by other content.', + labelMoreReferences: (count: number) => { + if (count === 1) return '...og en mere'; + return `...og ${count} andre`; + }, }, logViewer: { deleteSavedSearch: 'Slet gemte søgning', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts index b292ea7099..6e91f79474 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts @@ -255,6 +255,7 @@ export default { 'Publish %0% and all content items underneath and thereby making their content publicly available.', publishDescendantsWithVariantsHelp: 'Publish variants and variants of same type underneath and thereby making their content publicly available.', + noVariantsToProcess: 'There are no available variants', releaseDate: 'Publish at', unpublishDate: 'Unpublish at', removeDate: 'Clear Date', @@ -308,7 +309,7 @@ export default { languagesToSendForApproval: 'What languages would you like to send for approval?', languagesToSchedule: 'What languages would you like to schedule?', languagesToUnpublish: - 'Select the languages to unpublish. Unpublishing a mandatory language will\n unpublish all languages.\n ', + 'Select the languages to unpublish. Unpublishing a mandatory language will unpublish all languages.', variantsWillBeSaved: 'All new variants will be saved.', variantsToPublish: 'Which variants would you like to publish?', variantsToSave: 'Choose which variants to be saved.', @@ -2172,6 +2173,10 @@ export default { 'This item or its descendants is being referenced. Unpublishing can lead to broken links on your website. Please take the appropriate actions.', deleteDisabledWarning: 'This item or its descendants is being referenced. Therefore, deletion has been disabled.', listViewDialogWarning: 'The following items you are trying to %0% are referenced by other content.', + labelMoreReferences: (count: number) => { + if (count === 1) return '...and one more item'; + return `...and ${count} more items`; + }, }, logViewer: { deleteSavedSearch: 'Delete Saved Search', diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/index.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/index.ts index 0d1532cf44..26cb637132 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/index.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/index.ts @@ -284,6 +284,7 @@ export type { PagedProblemDetailsModel } from './models/PagedProblemDetailsModel export type { PagedRedirectUrlResponseModel } from './models/PagedRedirectUrlResponseModel'; export type { PagedRelationItemResponseModel } from './models/PagedRelationItemResponseModel'; export type { PagedRelationResponseModel } from './models/PagedRelationResponseModel'; +export type { PagedRelationTypeTreeItemResponseModel } from './models/PagedRelationTypeTreeItemResponseModel'; export type { PagedSavedLogSearchResponseModel } from './models/PagedSavedLogSearchResponseModel'; export type { PagedSearcherResponseModel } from './models/PagedSearcherResponseModel'; export type { PagedSearchResultResponseModel } from './models/PagedSearchResultResponseModel'; @@ -291,6 +292,7 @@ export type { PagedTagResponseModel } from './models/PagedTagResponseModel'; export type { PagedTelemetryResponseModel } from './models/PagedTelemetryResponseModel'; export type { PagedUserGroupResponseModel } from './models/PagedUserGroupResponseModel'; export type { PagedUserResponseModel } from './models/PagedUserResponseModel'; +export type { PagedWebhookResponseModel } from './models/PagedWebhookResponseModel'; export type { PartialViewFolderResponseModel } from './models/PartialViewFolderResponseModel'; export type { PartialViewItemResponseModel } from './models/PartialViewItemResponseModel'; export type { PartialViewResponseModel } from './models/PartialViewResponseModel'; @@ -320,6 +322,7 @@ export type { RelationResponseModel } from './models/RelationResponseModel'; export type { RelationTypeBaseModel } from './models/RelationTypeBaseModel'; export type { RelationTypeItemResponseModel } from './models/RelationTypeItemResponseModel'; export type { RelationTypeResponseModel } from './models/RelationTypeResponseModel'; +export type { RelationTypeTreeItemResponseModel } from './models/RelationTypeTreeItemResponseModel'; export type { RenamePartialViewRequestModel } from './models/RenamePartialViewRequestModel'; export type { RenameScriptRequestModel } from './models/RenameScriptRequestModel'; export type { RenameStylesheetRequestModel } from './models/RenameStylesheetRequestModel'; diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/ContentTreeItemResponseModel.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/ContentTreeItemResponseModel.ts index b2e1c8d529..bb5f9c106d 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/ContentTreeItemResponseModel.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/ContentTreeItemResponseModel.ts @@ -6,7 +6,6 @@ import type { ReferenceByIdModel } from './ReferenceByIdModel'; export type ContentTreeItemResponseModel = { - type: string; hasChildren: boolean; parent?: ReferenceByIdModel | null; noAccess: boolean; diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/PagedNamedEntityTreeItemResponseModel.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/PagedNamedEntityTreeItemResponseModel.ts index 8af4c598e0..c42a7ab504 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/PagedNamedEntityTreeItemResponseModel.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/PagedNamedEntityTreeItemResponseModel.ts @@ -9,9 +9,10 @@ import type { DocumentTypeTreeItemResponseModel } from './DocumentTypeTreeItemRe import type { FolderTreeItemResponseModel } from './FolderTreeItemResponseModel'; import type { MediaTypeTreeItemResponseModel } from './MediaTypeTreeItemResponseModel'; import type { NamedEntityTreeItemResponseModel } from './NamedEntityTreeItemResponseModel'; +import type { RelationTypeTreeItemResponseModel } from './RelationTypeTreeItemResponseModel'; export type PagedNamedEntityTreeItemResponseModel = { total: number; - items: Array<(NamedEntityTreeItemResponseModel | DataTypeTreeItemResponseModel | DocumentBlueprintTreeItemResponseModel | DocumentTypeTreeItemResponseModel | FolderTreeItemResponseModel | MediaTypeTreeItemResponseModel)>; + items: Array<(NamedEntityTreeItemResponseModel | DataTypeTreeItemResponseModel | DocumentBlueprintTreeItemResponseModel | DocumentTypeTreeItemResponseModel | FolderTreeItemResponseModel | MediaTypeTreeItemResponseModel | RelationTypeTreeItemResponseModel)>; }; diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/PagedRelationTypeTreeItemResponseModel.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/PagedRelationTypeTreeItemResponseModel.ts new file mode 100644 index 0000000000..7790bc4b16 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/PagedRelationTypeTreeItemResponseModel.ts @@ -0,0 +1,12 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { RelationTypeTreeItemResponseModel } from './RelationTypeTreeItemResponseModel'; + +export type PagedRelationTypeTreeItemResponseModel = { + total: number; + items: Array; +}; + diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/PagedWebhookResponseModel.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/PagedWebhookResponseModel.ts new file mode 100644 index 0000000000..1efc883d28 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/PagedWebhookResponseModel.ts @@ -0,0 +1,12 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { WebhookResponseModel } from './WebhookResponseModel'; + +export type PagedWebhookResponseModel = { + total: number; + items: Array; +}; + diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/RelationTypeItemResponseModel.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/RelationTypeItemResponseModel.ts index f156f0987d..1f9bcdfe1e 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/RelationTypeItemResponseModel.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/RelationTypeItemResponseModel.ts @@ -5,5 +5,7 @@ import type { NamedItemResponseModelBaseModel } from './NamedItemResponseModelBaseModel'; -export type RelationTypeItemResponseModel = NamedItemResponseModelBaseModel; +export type RelationTypeItemResponseModel = (NamedItemResponseModelBaseModel & { + isDeletable: boolean; +}); diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/RelationTypeResponseModel.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/RelationTypeResponseModel.ts index 990febe8b7..b20537c5d0 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/RelationTypeResponseModel.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/RelationTypeResponseModel.ts @@ -9,7 +9,7 @@ export type RelationTypeResponseModel = (RelationTypeBaseModel & { id: string; alias?: string | null; path: string; - isSystemRelationType: boolean; + isDeletable: boolean; parentObjectTypeName?: string | null; childObjectTypeName?: string | null; }); diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/RelationTypeTreeItemResponseModel.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/RelationTypeTreeItemResponseModel.ts new file mode 100644 index 0000000000..c1c4ce7465 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/RelationTypeTreeItemResponseModel.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { NamedEntityTreeItemResponseModel } from './NamedEntityTreeItemResponseModel'; + +export type RelationTypeTreeItemResponseModel = (NamedEntityTreeItemResponseModel & { + isDeletable: boolean; +}); + diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/TreeItemPresentationModel.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/TreeItemPresentationModel.ts index b6e40540ce..3f84670b27 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/TreeItemPresentationModel.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/TreeItemPresentationModel.ts @@ -4,7 +4,6 @@ /* eslint-disable */ export type TreeItemPresentationModel = { - type: string; hasChildren: boolean; }; diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/RelationTypeResource.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/RelationTypeResource.ts index bcbb7f7c50..a3f6109d3a 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/RelationTypeResource.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/RelationTypeResource.ts @@ -3,7 +3,7 @@ /* tslint:disable */ /* eslint-disable */ import type { CreateRelationTypeRequestModel } from '../models/CreateRelationTypeRequestModel'; -import type { PagedNamedEntityTreeItemResponseModel } from '../models/PagedNamedEntityTreeItemResponseModel'; +import type { PagedRelationTypeTreeItemResponseModel } from '../models/PagedRelationTypeTreeItemResponseModel'; import type { RelationTypeItemResponseModel } from '../models/RelationTypeItemResponseModel'; import type { RelationTypeResponseModel } from '../models/RelationTypeResponseModel'; import type { UpdateRelationTypeRequestModel } from '../models/UpdateRelationTypeRequestModel'; @@ -130,7 +130,7 @@ export class RelationTypeResource { } /** - * @returns PagedNamedEntityTreeItemResponseModel Success + * @returns PagedRelationTypeTreeItemResponseModel Success * @throws ApiError */ public static getTreeRelationTypeRoot({ @@ -139,7 +139,7 @@ export class RelationTypeResource { }: { skip?: number, take?: number, - }): CancelablePromise { + }): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/umbraco/management/api/v1/tree/relation-type/root', diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/WebhookResource.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/WebhookResource.ts index bbe951c01d..4e7f80c31f 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/WebhookResource.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/WebhookResource.ts @@ -3,6 +3,7 @@ /* tslint:disable */ /* eslint-disable */ import type { CreateWebhookRequestModel } from '../models/CreateWebhookRequestModel'; +import type { PagedWebhookResponseModel } from '../models/PagedWebhookResponseModel'; import type { UpdateWebhookRequestModel } from '../models/UpdateWebhookRequestModel'; import type { WebhookItemResponseModel } from '../models/WebhookItemResponseModel'; import type { WebhookResponseModel } from '../models/WebhookResponseModel'; @@ -13,6 +14,30 @@ import { request as __request } from '../core/request'; export class WebhookResource { + /** + * @returns PagedWebhookResponseModel Success + * @throws ApiError + */ + public static getWebhook({ + skip, + take = 100, + }: { + skip?: number, + take?: number, + }): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/umbraco/management/api/v1/webhook', + query: { + 'skip': skip, + 'take': take, + }, + errors: { + 401: `The resource is protected and requires an authentication token`, + }, + }); + } + /** * @returns string Created * @throws ApiError diff --git a/src/Umbraco.Web.UI.Client/src/libs/class-api/class.interface.ts b/src/Umbraco.Web.UI.Client/src/libs/class-api/class.interface.ts index 7235a74eb9..8b46eb957f 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/class-api/class.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/class-api/class.interface.ts @@ -31,7 +31,7 @@ export interface UmbClassInterface extends UmbControllerHost { // This type dance checks if the Observable given could be undefined, if it potentially could be undefined it means that this potentially could return undefined and then call the callback with undefined. [NL] source: ObservableType, callback: ObserverCallback, - controllerAlias?: UmbControllerAlias, + controllerAlias?: UmbControllerAlias | null, ): SpecificR; /** diff --git a/src/Umbraco.Web.UI.Client/src/libs/class-api/class.mixin.ts b/src/Umbraco.Web.UI.Client/src/libs/class-api/class.mixin.ts index ce5c4c6f5a..90a83a1c27 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/class-api/class.mixin.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/class-api/class.mixin.ts @@ -54,7 +54,7 @@ export const UmbClassMixin = >(superClas // This type dance checks if the Observable given could be undefined, if it potentially could be undefined it means that this potentially could return undefined and then call the callback with undefined. [NL] source: ObservableType, callback: ObserverCallback, - controllerAlias?: UmbControllerAlias, + controllerAlias?: UmbControllerAlias | null, ): SpecificR { // Fallback to use a hash of the provided method, but only if the alias is undefined. controllerAlias ??= controllerAlias === undefined ? simpleHashCode(callback.toString()) : undefined; diff --git a/src/Umbraco.Web.UI.Client/src/libs/element-api/element.mixin.ts b/src/Umbraco.Web.UI.Client/src/libs/element-api/element.mixin.ts index aea4582c81..aebd9bd789 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/element-api/element.mixin.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/element-api/element.mixin.ts @@ -27,7 +27,7 @@ export const UmbElementMixin = (superClass: T) // This type dance checks if the Observable given could be undefined, if it potentially could be undefined it means that this potentially could return undefined and then call the callback with undefined. [NL] source: ObservableType, callback: ObserverCallback, - controllerAlias?: UmbControllerAlias, + controllerAlias?: UmbControllerAlias | null, ): SpecificR { // Fallback to use a hash of the provided method, but only if the alias is undefined. controllerAlias ??= controllerAlias === undefined ? simpleHashCode(callback.toString()) : undefined; diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts index 1459943d78..245ddf6667 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts @@ -248,8 +248,8 @@ export class UmbExtensionRegistry< } /** - * Get an observable that provides extensions matching the given alias. - * @param alias {string} - The alias of the extensions to get. + * Get an observable that provides an extension matching the given alias. + * @param alias {string} - The alias of the extension to get. * @returns {Observable} - An observable of the extension that matches the alias. */ byAlias(alias: string): Observable { @@ -267,6 +267,19 @@ export class UmbExtensionRegistry< ) as Observable; } + /** + * Get an extension that matches the given alias, this will not return an observable, it is a one of retrieval if it exists at the given point in time. + * @param alias {string} - The alias of the extension to get. + * @returns {} - The extension manifest that matches the alias. + */ + getByAlias(alias: string): T | undefined { + const ext = this._extensions.getValue().find((ext) => ext.alias === alias) as T | undefined; + if (ext?.kind) { + return this.#mergeExtensionWithKinds([ext, this._kinds.getValue()]); + } + return ext; + } + /** * Get an observable that provides extensions matching the given type and alias. * @param type {string} - The type of the extensions to get. diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts index fdd5689913..0bf1596d9b 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.controller.ts @@ -13,8 +13,8 @@ export class UmbObserverController extends UmbObserver implement constructor( host: UmbControllerHost, source: Observable, - callback: ObserverCallback, - alias: UmbControllerAlias, + callback?: ObserverCallback, + alias?: UmbControllerAlias, ) { super(source, callback); this.#host = host; diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.ts index f9bee2b51b..19e86e2874 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/observer.ts @@ -15,9 +15,11 @@ export class UmbObserver { #callback!: ObserverCallback; #subscription!: Subscription; - constructor(source: Observable, callback: ObserverCallback) { + constructor(source: Observable, callback?: ObserverCallback) { this.#source = source; - this.#subscription = source.subscribe(callback); + if (callback) { + this.#subscription = source.subscribe(callback); + } } /** @@ -63,9 +65,9 @@ export class UmbObserver { destroy(): void { if (this.#subscription) { this.#subscription.unsubscribe(); - (this.#source as any) = undefined; (this.#callback as any) = undefined; (this.#subscription as any) = undefined; } + (this.#source as any) = undefined; } } diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/assign-to-frozen-object.function.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/assign-to-frozen-object.function.ts new file mode 100644 index 0000000000..f0f2665bbb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/assign-to-frozen-object.function.ts @@ -0,0 +1,3 @@ +export function assignToFrozenObject(target: T, source: Partial): T { + return Object.assign(Object.create(Object.getPrototypeOf(target)), target, source); +} diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts index 0c77207194..f207030636 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts @@ -1,4 +1,5 @@ export * from './append-to-frozen-array.function.js'; +export * from './assign-to-frozen-object.function.js'; export * from './create-observable-part.function.js'; export * from './deep-freeze.function.js'; export * from './default-memoization.function.js'; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts index 52f12c19dc..b7f23ccf80 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts @@ -4,9 +4,7 @@ import type { DataTypeTreeItemResponseModel, } from '@umbraco-cms/backoffice/external/backend-api'; -type UmbMockDataTypeModelHack = DataTypeResponseModel & DataTypeTreeItemResponseModel & DataTypeItemResponseModel; - -export interface UmbMockDataTypeModel extends Omit {} +export type UmbMockDataTypeModel = DataTypeResponseModel & DataTypeTreeItemResponseModel & DataTypeItemResponseModel; export const data: Array = [ { @@ -367,7 +365,7 @@ export const data: Array = [ name: 'Multiple Text String', id: 'dt-multipleTextString', parent: null, - editorAlias: 'Umbraco.MultipleTextString', + editorAlias: 'Umbraco.MultipleTextstring', editorUiAlias: 'Umb.PropertyEditorUi.MultipleTextString', hasChildren: false, isFolder: false, diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/dictionary/dictionary.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/dictionary/dictionary.data.ts index b135a1b516..3f68fa97f5 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/dictionary/dictionary.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/dictionary/dictionary.data.ts @@ -5,13 +5,11 @@ import type { NamedEntityTreeItemResponseModel, } from '@umbraco-cms/backoffice/external/backend-api'; -type UmbMockDictionaryModelHack = DictionaryItemResponseModel & +export type UmbMockDictionaryModel = DictionaryItemResponseModel & NamedEntityTreeItemResponseModel & DictionaryItemItemResponseModel & DictionaryOverviewResponseModel; -export interface UmbMockDictionaryModel extends Omit {} - export const data: Array = [ { name: 'Hello', diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/dictionary/dictionary.db.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/dictionary/dictionary.db.ts index 191f854604..bc8977c9ab 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/dictionary/dictionary.db.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/dictionary/dictionary.db.ts @@ -38,7 +38,7 @@ export class UmbDictionaryMockDB extends UmbEntityMockDbBase => { +const treeItemMapper = (model: UmbMockDictionaryModel): NamedEntityTreeItemResponseModel => { return { name: model.name, id: model.id, diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts index 182e04ef9c..ac9dabac6b 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts @@ -5,12 +5,10 @@ import type { DocumentTypeTreeItemResponseModel, } from '@umbraco-cms/backoffice/external/backend-api'; -type UmbMockDocumentTypeModelHack = DocumentTypeResponseModel & +export type UmbMockDocumentTypeModel = DocumentTypeResponseModel & DocumentTypeTreeItemResponseModel & DocumentTypeItemResponseModel; -export interface UmbMockDocumentTypeModel extends Omit {} - export const data: Array = [ { allowedTemplates: [], @@ -1081,7 +1079,7 @@ export const data: Array = [ dataType: { id: '0cc0eba1-9960-42c9-bf9b-60e150b429ae' }, variesByCulture: false, variesBySegment: false, - sortOrder: 0, + sortOrder: 1, validation: { mandatory: false, mandatoryMessage: null, @@ -1101,7 +1099,7 @@ export const data: Array = [ dataType: { id: '0cc0eba1-9960-42c9-bf9b-60e150b429ae' }, variesByCulture: false, variesBySegment: false, - sortOrder: 0, + sortOrder: 2, validation: { mandatory: false, mandatoryMessage: null, @@ -1126,7 +1124,7 @@ export const data: Array = [ parent: null, name: 'Alchemy', type: 'Group', - sortOrder: 0, + sortOrder: 1, }, ], allowedDocumentTypes: [], @@ -1389,7 +1387,7 @@ export const data: Array = [ dataType: { id: 'dt-textBox' }, variesByCulture: false, variesBySegment: false, - sortOrder: 10, + sortOrder: 0, validation: { mandatory: true, mandatoryMessage: null, @@ -1409,7 +1407,7 @@ export const data: Array = [ dataType: { id: 'dt-integer' }, variesByCulture: false, variesBySegment: false, - sortOrder: 10, + sortOrder: 1, validation: { mandatory: true, mandatoryMessage: null, @@ -1463,7 +1461,7 @@ export const data: Array = [ dataType: { id: 'dt-textBox' }, variesByCulture: false, variesBySegment: false, - sortOrder: 10, + sortOrder: 0, validation: { mandatory: true, mandatoryMessage: null, @@ -1517,7 +1515,7 @@ export const data: Array = [ dataType: { id: 'dt-mediaPicker' }, variesByCulture: false, variesBySegment: false, - sortOrder: 10, + sortOrder: 0, validation: { mandatory: true, mandatoryMessage: null, @@ -1571,7 +1569,7 @@ export const data: Array = [ dataType: { id: 'dt-richTextEditor' }, variesByCulture: false, variesBySegment: false, - sortOrder: 10, + sortOrder: 0, validation: { mandatory: true, mandatoryMessage: null, diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.db.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.db.ts index 47428a85b5..24a362a1cb 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.db.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.db.ts @@ -122,9 +122,7 @@ const documentTypeDetailMapper = (item: UmbMockDocumentTypeModel): DocumentTypeR }; }; -const documentTypeTreeItemMapper = ( - item: UmbMockDocumentTypeModel, -): Omit => { +const documentTypeTreeItemMapper = (item: UmbMockDocumentTypeModel): DocumentTypeTreeItemResponseModel => { return { name: item.name, hasChildren: item.hasChildren, diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts index cf47af845a..1c0a6b60a2 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts @@ -5,9 +5,7 @@ import type { } from '@umbraco-cms/backoffice/external/backend-api'; import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; -type UmbMockDocumentTypeModelHack = DocumentResponseModel & DocumentTreeItemResponseModel & DocumentItemResponseModel; - -export interface UmbMockDocumentModel extends Omit {} +export type UmbMockDocumentModel = DocumentResponseModel & DocumentTreeItemResponseModel & DocumentItemResponseModel; export const data: Array = [ { diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.db.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.db.ts index 76e2dfaafd..fdd0e3f23e 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.db.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.db.ts @@ -41,7 +41,7 @@ export class UmbDocumentMockDB extends UmbEntityMockDbBase } } -const treeItemMapper = (model: UmbMockDocumentModel): Omit => { +const treeItemMapper = (model: UmbMockDocumentModel): DocumentTreeItemResponseModel => { const documentType = umbDocumentTypeMockDb.read(model.documentType.id); if (!documentType) throw new Error(`Document type with id ${model.documentType.id} not found`); diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/log-viewer.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/log-viewer.data.ts index 33f66fb115..fa179f05e7 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/log-viewer.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/log-viewer.data.ts @@ -14,7 +14,7 @@ class UmbLogViewerSearchesData extends UmbMockDBBase { - return this.data.slice(skip, take); + return this.data.slice(skip, take + skip); } getByName(name: string) { @@ -29,7 +29,7 @@ class UmbLogViewerTemplatesData extends UmbMockDBBase // skip can be number or null getTemplates(skip = 0, take = this.data.length): Array { - return this.data.slice(skip, take); + return this.data.slice(skip, take + skip); } } diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.data.ts index a7d235712b..aeba10c756 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.data.ts @@ -4,9 +4,9 @@ import type { MediaTypeTreeItemResponseModel, } from '@umbraco-cms/backoffice/external/backend-api'; -type UmbMockMediaTypeModelHack = MediaTypeResponseModel & MediaTypeTreeItemResponseModel & MediaTypeItemResponseModel; - -export interface UmbMockMediaTypeModel extends Omit {} +export type UmbMockMediaTypeModel = MediaTypeResponseModel & + MediaTypeTreeItemResponseModel & + MediaTypeItemResponseModel; export const data: Array = [ { @@ -91,9 +91,7 @@ export const data: Array = [ variesByCulture: false, variesBySegment: false, isElement: false, - allowedMediaTypes: [ - { mediaType: { id: 'media-type-1-id' }, sortOrder: 0 }, - ], + allowedMediaTypes: [{ mediaType: { id: 'media-type-1-id' }, sortOrder: 0 }], compositions: [], isFolder: false, hasChildren: false, diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.db.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.db.ts index 630e0d7e7c..874a4e4374 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.db.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.db.ts @@ -110,7 +110,7 @@ const mediaTypeDetailMapper = (item: UmbMockMediaTypeModel): MediaTypeResponseMo }; }; -const mediaTypeTreeItemMapper = (item: UmbMockMediaTypeModel): Omit => { +const mediaTypeTreeItemMapper = (item: UmbMockMediaTypeModel): MediaTypeTreeItemResponseModel => { return { name: item.name, hasChildren: item.hasChildren, diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts index 211c9aa3f2..012ab54620 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.data.ts @@ -4,9 +4,7 @@ import type { MediaTreeItemResponseModel, } from '@umbraco-cms/backoffice/external/backend-api'; -type UmbMockMediaModelHack = MediaResponseModel & MediaTreeItemResponseModel & MediaItemResponseModel; - -export interface UmbMockMediaModel extends Omit {} +export type UmbMockMediaModel = MediaResponseModel & MediaTreeItemResponseModel & MediaItemResponseModel; export const data: Array = [ { diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.db.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.db.ts index a87acd02d9..e0a6337152 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.db.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/media/media.db.ts @@ -28,7 +28,7 @@ export class UmbMediaMockDB extends UmbEntityMockDbBase { } } -const treeItemMapper = (model: UmbMockMediaModel): Omit => { +const treeItemMapper = (model: UmbMockMediaModel): MediaTreeItemResponseModel => { const mediaType = umbMediaTypeMockDb.read(model.mediaType.id); if (!mediaType) throw new Error(`Media type with id ${model.mediaType.id} not found`); diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/partial-view/partial-view.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/partial-view/partial-view.data.ts index 86efd983d6..0fa1bf78c7 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/partial-view/partial-view.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/partial-view/partial-view.data.ts @@ -5,12 +5,10 @@ import type { PartialViewSnippetResponseModel, } from '@umbraco-cms/backoffice/external/backend-api'; -type UmbMockPartialViewModelHack = PartialViewResponseModel & +export type UmbMockPartialViewModel = PartialViewResponseModel & FileSystemTreeItemPresentationModel & PartialViewItemResponseModel; -export interface UmbMockPartialViewModel extends Omit {} - export const data: Array = [ { name: 'blockgrid', diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/relations/relation-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/relations/relation-type.data.ts index ae17352881..081b6e1f9c 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/relations/relation-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/relations/relation-type.data.ts @@ -12,7 +12,7 @@ export const data: Array = [ alias: 'relateDocumentOnCopy', name: 'Relate Document On Copy', path: '', - isSystemRelationType: true, + isDeletable: true, isBidirectional: false, isDependency: false, parentObjectType: 'Document', @@ -25,7 +25,7 @@ export const data: Array = [ alias: 'relateParentDocumentOnDelete', name: 'Relate Parent Document On Delete', path: '', - isSystemRelationType: true, + isDeletable: true, isBidirectional: false, isDependency: false, parentObjectType: 'Document', @@ -38,7 +38,7 @@ export const data: Array = [ alias: 'relateParentMediaFolderOnDelete', name: 'Relate Parent Media Folder On Delete', path: '', - isSystemRelationType: true, + isDeletable: true, isBidirectional: false, isDependency: false, parentObjectType: 'Document', @@ -51,7 +51,7 @@ export const data: Array = [ alias: 'relatedMedia', name: 'Related Media', path: '', - isSystemRelationType: true, + isDeletable: true, isBidirectional: false, isDependency: false, parentObjectType: 'Document', @@ -64,7 +64,7 @@ export const data: Array = [ alias: 'relatedDocument', name: 'Related Document', path: '', - isSystemRelationType: true, + isDeletable: true, isBidirectional: false, isDependency: false, parentObjectType: 'Document', @@ -79,7 +79,6 @@ export const treeData: Array = [ id: 'e0d39ff5-71d8-453f-b682-9d8d31ee5e06', parent: null, name: 'Relate Document On Copy', - type: 'relation-type', hasChildren: false, }, { @@ -87,28 +86,24 @@ export const treeData: Array = [ parent: null, name: 'Relate Parent Document On Delete', - type: 'relation-type', hasChildren: false, }, { id: '6f9b800c-762c-42d4-85d9-bf40a77d689e', parent: null, name: 'Relate Parent Media Folder On Delete', - type: 'relation-type', hasChildren: false, }, { id: 'd421727d-43de-4205-b4c6-037404f309ad', parent: null, name: 'Related Media', - type: 'relation-type', hasChildren: false, }, { id: 'e9a0a28e-2d5b-4229-ac00-66f2df230513', parent: null, name: 'Related Document', - type: 'relation-type', hasChildren: false, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/script/script.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/script/script.data.ts index a71cfe367d..24b7fb53e0 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/script/script.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/script/script.data.ts @@ -4,9 +4,7 @@ import type { ScriptResponseModel, } from '@umbraco-cms/backoffice/external/backend-api'; -type UmbMockScriptModelHack = ScriptResponseModel & FileSystemTreeItemPresentationModel & ScriptItemResponseModel; - -export interface UmbMockScriptModel extends Omit {} +export type UmbMockScriptModel = ScriptResponseModel & FileSystemTreeItemPresentationModel & ScriptItemResponseModel; export const data: Array = [ { diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/static-file/static-file.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/static-file/static-file.data.ts index 052cdaca11..b0eb91d277 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/static-file/static-file.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/static-file/static-file.data.ts @@ -3,8 +3,7 @@ import type { StaticFileItemResponseModel, } from '@umbraco-cms/backoffice/external/backend-api'; -type UmbMockStaticFileModelHack = StaticFileItemResponseModel & FileSystemTreeItemPresentationModel; -export interface UmbMockStaticFileModel extends Omit {} +export type UmbMockStaticFileModel = StaticFileItemResponseModel & FileSystemTreeItemPresentationModel; export const data: Array = [ { diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/stylesheet/stylesheet.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/stylesheet/stylesheet.data.ts index 2ce1bcde20..1145bb66e9 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/stylesheet/stylesheet.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/stylesheet/stylesheet.data.ts @@ -4,12 +4,10 @@ import type { StylesheetResponseModel, } from '@umbraco-cms/backoffice/external/backend-api'; -type UmbMockStylesheetModelHack = StylesheetResponseModel & +export type UmbMockStylesheetModel = StylesheetResponseModel & FileSystemTreeItemPresentationModel & StylesheetItemResponseModel; -export interface UmbMockStylesheetModel extends Omit {} - export const data: Array = [ { name: 'Stylesheet File 1.css', diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/template/template.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/template/template.data.ts index fd6988429a..4aadfd758e 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/template/template.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/template/template.data.ts @@ -7,9 +7,7 @@ import type { } from '@umbraco-cms/backoffice/external/backend-api'; import { TemplateQueryPropertyTypeModel, OperatorModel } from '@umbraco-cms/backoffice/external/backend-api'; -type UmbMockTemplateModelHack = TemplateResponseModel & NamedEntityTreeItemResponseModel & TemplateItemResponseModel; - -export interface UmbMockTemplateModel extends Omit {} +export type UmbMockTemplateModel = TemplateResponseModel & NamedEntityTreeItemResponseModel & TemplateItemResponseModel; export const data: Array = [ { diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/tracked-reference.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/tracked-reference.data.ts index 195999bc43..e49f6b009d 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/tracked-reference.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/tracked-reference.data.ts @@ -13,4 +13,16 @@ export const items: Array = [ relationTypeIsDependency: true, relationTypeName: 'Related Document', }, + { + nodeId: '1234', + nodeName: 'Image Block', + nodeType: 'element', + nodePublished: true, + contentTypeIcon: 'icon-settings', + contentTypeName: 'Image Block', + contentTypeAlias: 'imageBlock', + relationTypeIsBidirectional: false, + relationTypeIsDependency: true, + relationTypeName: 'Related Document', + }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.data.ts index 81a46cb651..10f4fcdda9 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.data.ts @@ -32,7 +32,15 @@ export const data: Array = [ document: { id: 'simple-document-id' }, }, ], - sections: [], + sections: [ + 'Umb.Section.Content', + 'Umb.Section.Media', + 'Umb.Section.Settings', + 'Umb.Section.Members', + 'Umb.Section.Packages', + 'Umb.Section.Translation', + 'Umb.Section.Users', + ], languages: [], hasAccessToAllLanguages: true, documentRootAccess: true, @@ -60,7 +68,7 @@ export const data: Array = [ 'Umb.Document.Rollback', ], permissions: [], - sections: [], + sections: ['Umb.Section.Content', 'Umb.Section.Media'], languages: [], hasAccessToAllLanguages: true, documentRootAccess: true, @@ -88,7 +96,7 @@ export const data: Array = [ documentStartNode: { id: 'all-property-editors-document-id' }, fallbackPermissions: ['Umb.Document.Read', 'Umb.Document.Update'], permissions: [], - sections: [], + sections: ['Umb.Section.Translation'], languages: [], hasAccessToAllLanguages: true, documentRootAccess: true, @@ -107,7 +115,7 @@ export const data: Array = [ 'Umb.Document.Notifications', ], permissions: [], - sections: [], + sections: ['Umb.Section.Content'], languages: [], hasAccessToAllLanguages: true, documentRootAccess: true, diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.db.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.db.ts index ad7daef15d..29d72987dc 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.db.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.db.ts @@ -38,6 +38,16 @@ export class UmbUserGroupMockDB extends UmbEntityMockDbBase JSON.stringify(e)))).map((e) => JSON.parse(e)); return uniqueArray; } + + getAllowedSections(userGroupIds: string[]): string[] { + const sections = this.data + .filter((userGroup) => userGroupIds.includes(userGroup.id)) + .map((userGroup) => (userGroup.sections?.length ? userGroup.sections : [])) + .flat(); + + // Remove duplicates + return Array.from(new Set(sections)); + } } const itemMapper = (item: UmbMockUserGroupModel): UserGroupItemResponseModel => { diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.db.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.db.ts index 0493571cf9..502d924d0b 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.db.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.db.ts @@ -52,6 +52,9 @@ class UmbUserMockDB extends UmbEntityMockDbBase { getCurrentUser(): CurrentUserResponseModel { const firstUser = this.data[0]; const permissions = firstUser.userGroupIds?.length ? umbUserGroupMockDb.getPermissions(firstUser.userGroupIds) : []; + const allowedSections = firstUser.userGroupIds?.length + ? umbUserGroupMockDb.getAllowedSections(firstUser.userGroupIds) + : []; return { id: firstUser.id, @@ -66,7 +69,7 @@ class UmbUserMockDB extends UmbEntityMockDbBase { mediaStartNodeIds: firstUser.mediaStartNodeIds, fallbackPermissions: [], permissions, - allowedSections: [], + allowedSections, }; } diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/utils.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/utils.ts index 54e1eee8ff..f3a9ac812e 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/utils.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/utils.ts @@ -7,7 +7,6 @@ import type { export const createEntityTreeItem = (item: any): NamedEntityTreeItemResponseModel => { return { name: item.name, - type: item.type, hasChildren: item.hasChildren, id: item.id, parent: item.parent, @@ -21,7 +20,7 @@ export const folderTreeItemMapper = (item: any): FolderTreeItemResponseModel => }; }; -export const createFileSystemTreeItem = (item: any): Omit => { +export const createFileSystemTreeItem = (item: any): FileSystemTreeItemPresentationModel => { return { path: item.path, parent: item.parent ?? null, diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/utils/entity/entity-folder.manager.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/utils/entity/entity-folder.manager.ts index 968fe7f8e7..87eaef9361 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/utils/entity/entity-folder.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/utils/entity/entity-folder.manager.ts @@ -6,7 +6,7 @@ import type { UpdateFolderResponseModel, } from '@umbraco-cms/backoffice/external/backend-api'; -export class UmbMockEntityFolderManager> { +export class UmbMockEntityFolderManager { #db: UmbEntityMockDbBase; #createMockFolderMapper: (request: CreateFolderRequestModel) => MockItemType; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/utils/entity/entity-recycle-bin.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/utils/entity/entity-recycle-bin.ts index fb6940cc7a..2cb6918b2d 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/utils/entity/entity-recycle-bin.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/utils/entity/entity-recycle-bin.ts @@ -2,9 +2,7 @@ import { UmbEntityMockDbBase } from './entity-base.js'; import { UmbMockEntityTreeManager } from './entity-tree.manager.js'; import type { ContentTreeItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; -export class UmbEntityRecycleBin< - MockType extends Omit, -> extends UmbEntityMockDbBase { +export class UmbEntityRecycleBin extends UmbEntityMockDbBase { tree; constructor(data: Array, treeItemMapper: (model: MockType) => any) { diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/utils/entity/entity-tree.manager.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/utils/entity/entity-tree.manager.ts index 38c08ccad5..def3178707 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/utils/entity/entity-tree.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/utils/entity/entity-tree.manager.ts @@ -3,7 +3,7 @@ import type { UmbEntityMockDbBase } from './entity-base.js'; import { UmbId } from '@umbraco-cms/backoffice/id'; import type { EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; -export class UmbMockEntityTreeManager> { +export class UmbMockEntityTreeManager { #db: UmbEntityMockDbBase; #treeItemMapper: (item: T) => any; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/utils/file-system/file-system-tree.manager.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/utils/file-system/file-system-tree.manager.ts index cad195d8f9..2641be20f2 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/utils/file-system/file-system-tree.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/utils/file-system/file-system-tree.manager.ts @@ -3,7 +3,7 @@ import { createFileSystemTreeItem } from '../../utils.js'; import { pagedResult } from '../paged-result.js'; import type { FileSystemTreeItemPresentationModel } from '@umbraco-cms/backoffice/external/backend-api'; -export class UmbMockFileSystemTreeManager> { +export class UmbMockFileSystemTreeManager { #db: UmbMockDBBase; constructor(mockDb: UmbMockDBBase) { @@ -11,7 +11,7 @@ export class UmbMockFileSystemTreeManager>; + items: Array; total: number; } { const items = this.#db.getAll().filter((item) => item.parent === null); @@ -19,7 +19,7 @@ export class UmbMockFileSystemTreeManager>; + items: Array; total: number; } { const items = this.#db.getAll().filter((item) => item.parent?.path === parentPath); diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/log-viewer.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/log-viewer.handlers.ts index c1aabd575f..acf457650e 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/log-viewer.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/log-viewer.handlers.ts @@ -14,7 +14,7 @@ export const handlers = [ const items = umbLogViewerData.searches.getSavedSearches(skipNumber, takeNumber); const response = { - total: items.length, + total: umbLogViewerData.searches.total, items, }; @@ -57,7 +57,7 @@ export const handlers = [ return res(ctx.delay(), ctx.status(200), ctx.json(response)); }), //#endregion - + //#region Logs rest.get(umbracoPath('/log-viewer/level'), (req, res, ctx) => { return res(ctx.delay(), ctx.status(200), ctx.json(umbLogViewerData.logLevels)); diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-manager.ts index 577909f5a8..afdfb4321b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-manager.ts @@ -1,7 +1,7 @@ import type { UmbBlockDataType } from '../types.js'; import { UmbBlockElementPropertyDatasetContext } from './block-element-property-dataset.context.js'; import type { UmbContentTypeModel } from '@umbraco-cms/backoffice/content-type'; -import { UmbContentTypePropertyStructureManager } from '@umbraco-cms/backoffice/content-type'; +import { UmbContentTypeStructureManager } from '@umbraco-cms/backoffice/content-type'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; @@ -19,7 +19,7 @@ export class UmbBlockElementManager extends UmbControllerBase { readonly unique = this.#data.asObservablePart((data) => data?.udi); readonly contentTypeId = this.#data.asObservablePart((data) => data?.contentTypeKey); - readonly structure = new UmbContentTypePropertyStructureManager( + readonly structure = new UmbContentTypeStructureManager( this, new UmbDocumentTypeDetailRepository(this), ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-content-no-router.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-content-no-router.element.ts index b3d5dce3ed..f4d7f0f520 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-content-no-router.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-content-no-router.element.ts @@ -37,7 +37,7 @@ export class UmbBlockWorkspaceViewEditContentNoRouterElement extends UmbLitEleme this.#tabsStructureHelper.setIsRoot(true); this.#tabsStructureHelper.setContainerChildType('Tab'); - this.observe(this.#tabsStructureHelper.containers, (tabs) => { + this.observe(this.#tabsStructureHelper.mergedContainers, (tabs) => { this._tabs = tabs; this._checkDefaultTabName(); }); @@ -98,7 +98,7 @@ export class UmbBlockWorkspaceViewEditContentNoRouterElement extends UmbLitEleme @click=${() => this.#setTabName(null, null)} >Content - ` + ` : ''} ${repeat( this._tabs, @@ -112,18 +112,18 @@ export class UmbBlockWorkspaceViewEditContentNoRouterElement extends UmbLitEleme >`; }, )} - ` + ` : ''} ${this._activeTabId !== undefined ? html` - ` + ` : ''} `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts index 0b587587ed..95b518f637 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts @@ -2,16 +2,16 @@ import { UMB_BLOCK_WORKSPACE_CONTEXT } from '../../block-workspace.context-token import type { UmbBlockWorkspaceElementManagerNames } from '../../block-workspace.context.js'; import { css, html, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import type { - UmbPropertyContainerTypes, - UmbContentTypeModel, - UmbPropertyTypeModel, -} from '@umbraco-cms/backoffice/content-type'; +import type { UmbContentTypeModel, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type'; import { UmbContentTypePropertyStructureHelper } from '@umbraco-cms/backoffice/content-type'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @customElement('umb-block-workspace-view-edit-properties') export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement { + #managerName?: UmbBlockWorkspaceElementManagerNames; + #blockWorkspace?: typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE; + #propertyStructureHelper = new UmbContentTypePropertyStructureHelper(this); + @property({ attribute: false }) public get managerName(): UmbBlockWorkspaceElementManagerNames | undefined { return this.#managerName; @@ -20,24 +20,13 @@ export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement { this.#managerName = value; this.#setStructureManager(); } - #managerName?: UmbBlockWorkspaceElementManagerNames; - #blockWorkspace?: typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE; - #propertyStructureHelper = new UmbContentTypePropertyStructureHelper(this); @property({ type: String, attribute: 'container-name', reflect: false }) - public get containerName(): string | undefined { - return this.#propertyStructureHelper.getContainerName(); + public get containerId(): string | null | undefined { + return this.#propertyStructureHelper.getContainerId(); } - public set containerName(value: string | undefined) { - this.#propertyStructureHelper.setContainerName(value); - } - - @property({ type: String, attribute: 'container-type', reflect: false }) - public get containerType(): UmbPropertyContainerTypes | undefined { - return this.#propertyStructureHelper.getContainerType(); - } - public set containerType(value: UmbPropertyContainerTypes | undefined) { - this.#propertyStructureHelper.setContainerType(value); + public set containerId(value: string | null | undefined) { + this.#propertyStructureHelper.setContainerId(value); } @state() diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-tab.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-tab.element.ts index c788c7ec28..7cf78cb5a3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-tab.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-tab.element.ts @@ -25,36 +25,15 @@ export class UmbBlockWorkspaceViewEditTabElement extends UmbLitElement { #groupStructureHelper = new UmbContentTypeContainerStructureHelper(this); @property({ type: String }) - public get tabName(): string | undefined { - return this.#groupStructureHelper.getName(); + public get containerId(): string | null | undefined { + return this._containerId; } - public set tabName(value: string | undefined) { - if (value === this._tabName) return; - const oldValue = this._tabName; - this._tabName = value; - this.#groupStructureHelper.setName(value); - this.requestUpdate('tabName', oldValue); - } - private _tabName?: string | undefined; - - @property({ type: Boolean }) - public get noTabName(): boolean { - return this.#groupStructureHelper.getIsRoot(); - } - public set noTabName(value: boolean) { - this.#groupStructureHelper.setIsRoot(value); - } - - private _ownerTabId?: string | null; - @property({ type: String }) - public get ownerTabId(): string | null | undefined { - return this._ownerTabId; - } - public set ownerTabId(value: string | null | undefined) { - if (value === this._ownerTabId) return; - this._ownerTabId = value; - this.#groupStructureHelper.setOwnerId(value); + public set containerId(value: string | null | undefined) { + this._containerId = value; + this.#groupStructureHelper.setContainerId(value); } + @state() + private _containerId?: string | null; /** * If true, the group box will be hidden, if we are to only represents one group. @@ -82,7 +61,7 @@ export class UmbBlockWorkspaceViewEditTabElement extends UmbLitElement { if (!this.#blockWorkspace || !this.#managerName) return; this.#groupStructureHelper.setStructureManager(this.#blockWorkspace[this.#managerName].structure); this.observe( - this.#groupStructureHelper.containers, + this.#groupStructureHelper.mergedContainers, (groups) => { this._groups = groups; }, @@ -98,30 +77,27 @@ export class UmbBlockWorkspaceViewEditTabElement extends UmbLitElement { } render() { - return html` - ${this._hasProperties ? this.#renderPart(this._tabName) : ''} - ${repeat( - this._groups, - (group) => group.name, - (group) => this.#renderPart(group.name, group.name), - )} - `; - } - - #renderPart(groupName: string | null | undefined, boxName?: string | null | undefined) { - return this.hideSingleGroup && this._groups.length === 1 - ? html` ` - : html` `; + return this._containerId + ? html` + ${this._hasProperties + ? html` ` + : ''} + ${repeat( + this._groups, + (group) => group.name, + (group) => + html` `, + )} + ` + : ''; } static styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit.element.ts index 54edca4e75..d5aae61dd7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit.element.ts @@ -74,7 +74,7 @@ export class UmbBlockWorkspaceViewEditElement extends UmbLitElement implements U 'observeGroups', ); this.observe( - this.#tabsStructureHelper.containers, + this.#tabsStructureHelper.mergedContainers, (tabs) => { this._tabs = tabs; this._createRoutes(); @@ -95,14 +95,7 @@ export class UmbBlockWorkspaceViewEditElement extends UmbLitElement implements U component: () => import('./block-workspace-view-edit-tab.element.js'), setup: (component) => { (component as UmbBlockWorkspaceViewEditTabElement).managerName = this.#managerName; - (component as UmbBlockWorkspaceViewEditTabElement).tabName = tabName; - // TODO: Consider if we can link these more simple, and not parse this on. - // Instead have the structure manager looking at wether one of the OwnerALikecontainers is in the owner document. - (component as UmbBlockWorkspaceViewEditTabElement).ownerTabId = this.#tabsStructureHelper.isOwnerContainer( - tab.id!, - ) - ? tab.id - : undefined; + (component as UmbBlockWorkspaceViewEditTabElement).containerId = tab.id; }, }); }); @@ -114,8 +107,7 @@ export class UmbBlockWorkspaceViewEditElement extends UmbLitElement implements U component: () => import('./block-workspace-view-edit-tab.element.js'), setup: (component) => { (component as UmbBlockWorkspaceViewEditTabElement).managerName = this.#managerName; - (component as UmbBlockWorkspaceViewEditTabElement).noTabName = true; - (component as UmbBlockWorkspaceViewEditTabElement).ownerTabId = null; + (component as UmbBlockWorkspaceViewEditTabElement).containerId = null; }, }); } @@ -144,7 +136,7 @@ export class UmbBlockWorkspaceViewEditElement extends UmbLitElement implements U href=${this._routerPath + '/'} >Content - ` + ` : ''} ${repeat( this._tabs, @@ -156,7 +148,7 @@ export class UmbBlockWorkspaceViewEditElement extends UmbLitElement implements U >`; }, )} - ` + ` : ''} `; + return html``; } static styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts index 929fb667ac..3bacd4e26a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts @@ -69,7 +69,7 @@ export class UmbCollectionDefaultElement extends UmbLitElement { } protected renderSelectionActions() { - return html``; + return html``; } static styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/body-layout/body-layout.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/body-layout/body-layout.element.ts index 00da0fd877..1d589df449 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/body-layout/body-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/body-layout/body-layout.element.ts @@ -15,6 +15,7 @@ import { /** * @element umb-body-layout * @description Layout element to arrange elements in a body layout. A general layout for most views. + * @slot - Slot for main content * @slot icon - Slot for icon * @slot name - Slot for name * @slot footer - Slot for footer element @@ -211,6 +212,11 @@ export class UmbBodyLayoutElement extends LitElement { overflow-y: auto; padding: var(--uui-size-layout-1); } + + #main > slot::slotted(*:first-child) { + padding-top: 0; + margin-top: 0; + } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/extension-with-api-slot/extension-with-api-slot.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/extension-with-api-slot/extension-with-api-slot.element.ts index 40248f67a3..1cd00a2c24 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/extension-with-api-slot/extension-with-api-slot.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/extension-with-api-slot/extension-with-api-slot.element.ts @@ -161,6 +161,7 @@ export class UmbExtensionWithApiSlotElement extends UmbLitElement { undefined, // We can leave the alias to undefined, as we destroy this our selfs. this.defaultElement, ); + this.#extensionsController.apiProperties = this.#apiProps; this.#extensionsController.elementProperties = this.#elProps; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/icon/icon.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/icon/icon.element.ts index 34e51da262..fdb61ad19c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/icon/icon.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/icon/icon.element.ts @@ -1,6 +1,6 @@ import { extractUmbColorVariable } from '../../resources/extractUmbColorVariable.function.js'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, property, state, ifDefined, css } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; /** @@ -45,7 +45,16 @@ export class UmbIconElement extends UmbLitElement { return html``; } - static styles = [UmbTextStyles]; + static styles = [ + UmbTextStyles, + css` + :host { + display: inline-block; + width: 1.15em; + height: 1.15em; + } + `, + ]; } declare global { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts index 9034604aff..a79524f077 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts @@ -15,6 +15,7 @@ export * from './history/index.js'; export * from './icon/index.js'; export * from './input-collection-configuration/index.js'; export * from './input-color/index.js'; +export * from './input-content-type-property/index.js'; export * from './input-date/index.js'; export * from './input-dropdown/index.js'; export * from './input-eye-dropper/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-content-type-property/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-content-type-property/index.ts new file mode 100644 index 0000000000..72923245b3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-content-type-property/index.ts @@ -0,0 +1 @@ +export * from './input-content-type-property.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-content-type-property/input-content-type-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-content-type-property/input-content-type-property.element.ts new file mode 100644 index 0000000000..8c870db936 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-content-type-property/input-content-type-property.element.ts @@ -0,0 +1,277 @@ +import { UmbDocumentTypePickerContext } from '../../../documents/document-types/components/input-document-type/input-document-type.context.js'; +import { UmbMediaTypePickerContext } from '../../../media/media-types/components/input-media-type/input-media-type.context.js'; +import { UmbMemberTypePickerContext } from '../../../members/member-type/components/input-member-type/input-member-type.context.js'; +import { html, customElement, property, css } from '@umbraco-cms/backoffice/external/lit'; +import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbDocumentTypeDetailRepository } from '@umbraco-cms/backoffice/document-type'; +import { UmbMediaTypeDetailRepository } from '@umbraco-cms/backoffice/media-type'; +import { UmbMemberTypeDetailRepository } from '@umbraco-cms/backoffice/member-type'; +import { UMB_ITEM_PICKER_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import type { UmbContentTypeModel } from '@umbraco-cms/backoffice/content-type'; +import type { UmbDetailRepositoryBase } from '@umbraco-cms/backoffice/repository'; +import type { UmbItemPickerModel } from '@umbraco-cms/backoffice/modal'; +import type { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; + +export type UmbContentTypePropertyValue = { + label: string; + alias: string; + isSystem: boolean; +}; + +type UmbInputContentTypePropertyConfigurationItem = { + item: UmbItemPickerModel; + pickerContext(): UmbPickerInputContext; + pickableFilter?(item: any): boolean; + repository(): UmbDetailRepositoryBase; + systemProperties?: Array; +}; + +type UmbInputContentTypePropertyConfiguration = { + documentTypes: UmbInputContentTypePropertyConfigurationItem; + elementTypes: UmbInputContentTypePropertyConfigurationItem; + mediaTypes: UmbInputContentTypePropertyConfigurationItem; + memberTypes: UmbInputContentTypePropertyConfigurationItem; +}; + +@customElement('umb-input-content-type-property') +export class UmbInputContentTypePropertyElement extends FormControlMixin(UmbLitElement) { + #configuration: UmbInputContentTypePropertyConfiguration = { + documentTypes: { + item: { + label: this.localize.term('content_documentType'), + description: this.localize.term('defaultdialogs_selectContentType'), + value: 'documentTypes', + }, + pickerContext: () => new UmbDocumentTypePickerContext(this), + pickableFilter: (docType) => !docType.isElement, + repository: () => new UmbDocumentTypeDetailRepository(this), + systemProperties: [ + { + label: this.localize.term('content_documentType'), + description: 'contentTypeAlias', + value: 'contentTypeAlias', + }, + { label: this.localize.term('content_createDate'), description: 'createDate', value: 'createDate' }, + { label: this.localize.term('content_createBy'), description: 'owner', value: 'owner' }, + { label: this.localize.term('content_isPublished'), description: 'published', value: 'published' }, + { label: this.localize.term('general_sort'), description: 'sortOrder', value: 'sortOrder' }, + { label: this.localize.term('content_updateDate'), description: 'updateDate', value: 'updateDate' }, + { label: this.localize.term('content_updatedBy'), description: 'updater', value: 'updater' }, + ], + }, + elementTypes: { + item: { + label: this.localize.term('create_elementType'), + description: this.localize.term('content_nestedContentSelectElementTypeModalTitle'), + value: 'elementTypes', + }, + pickerContext: () => new UmbDocumentTypePickerContext(this), + pickableFilter: (docType) => docType.isElement, + repository: () => new UmbDocumentTypeDetailRepository(this), + systemProperties: [ + { + label: this.localize.term('content_documentType'), + description: 'contentTypeAlias', + value: 'contentTypeAlias', + }, + ], + }, + mediaTypes: { + item: { + label: this.localize.term('content_mediatype'), + description: this.localize.term('defaultdialogs_selectMediaType'), + value: 'mediaTypes', + }, + pickerContext: () => new UmbMediaTypePickerContext(this), + repository: () => new UmbMediaTypeDetailRepository(this), + systemProperties: [ + { + label: this.localize.term('content_documentType'), + description: 'contentTypeAlias', + value: 'contentTypeAlias', + }, + { label: this.localize.term('content_createDate'), description: 'createDate', value: 'createDate' }, + { label: this.localize.term('content_createBy'), description: 'owner', value: 'owner' }, + { label: this.localize.term('general_sort'), description: 'sortOrder', value: 'sortOrder' }, + { label: this.localize.term('content_updateDate'), description: 'updateDate', value: 'updateDate' }, + { label: this.localize.term('content_updatedBy'), description: 'updater', value: 'updater' }, + ], + }, + memberTypes: { + item: { + label: this.localize.term('content_membertype'), + description: this.localize.term('defaultdialogs_selectMemberType'), + value: 'memberTypes', + }, + pickerContext: () => new UmbMemberTypePickerContext(this), + repository: () => new UmbMemberTypeDetailRepository(this), + systemProperties: [ + { + label: this.localize.term('content_documentType'), + description: 'contentTypeAlias', + value: 'contentTypeAlias', + }, + { label: this.localize.term('content_createDate'), description: 'createDate', value: 'createDate' }, + { label: this.localize.term('general_email'), description: 'email', value: 'email' }, + { label: this.localize.term('content_updateDate'), description: 'updateDate', value: 'updateDate' }, + { label: this.localize.term('general_username'), description: 'username', value: 'username' }, + ], + }, + }; + + #modalManager?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE; + + protected getFormElement() { + return undefined; + } + + public selectedProperty?: UmbContentTypePropertyValue; + + @property({ type: Boolean, attribute: 'document-types' }) + public documentTypes: boolean = false; + + @property({ type: Boolean, attribute: 'element-types' }) + public elementTypes: boolean = false; + + @property({ type: Boolean, attribute: 'media-types' }) + public mediaTypes: boolean = false; + + @property({ type: Boolean, attribute: 'member-types' }) + public memberTypes: boolean = false; + + constructor() { + super(); + + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (modalManager) => { + this.#modalManager = modalManager; + }); + } + + async #onClick() { + const items: Array = []; + + if (this.documentTypes) { + items.push(this.#configuration['documentTypes'].item); + } + + if (this.elementTypes) { + items.push(this.#configuration['elementTypes'].item); + } + + if (this.mediaTypes) { + items.push(this.#configuration['mediaTypes'].item); + } + + if (this.memberTypes) { + items.push(this.#configuration['memberTypes'].item); + } + + if (items.length === 1) { + // If there is only one item, we can skip the modal and go directly to the picker. + this.#openContentTypePicker(items[0].value as keyof UmbInputContentTypePropertyConfiguration); + return; + } + + if (!this.#modalManager) return; + + const modalContext = this.#modalManager.open(this, UMB_ITEM_PICKER_MODAL, { + data: { + headline: this.localize.term('defaultdialogs_selectContentType'), + items: items, + }, + }); + + const modalValue = await modalContext.onSubmit(); + + if (!modalValue) return; + + const configKey = modalValue.value as keyof UmbInputContentTypePropertyConfiguration; + this.#openContentTypePicker(configKey); + } + + async #openContentTypePicker(configKey: keyof UmbInputContentTypePropertyConfiguration) { + const config = this.#configuration[configKey]; + if (!config) return; + + const pickerContext = config.pickerContext(); + + pickerContext.max = 1; + + await pickerContext.openPicker({ + hideTreeRoot: true, + multiple: false, + pickableFilter: config.pickableFilter, + }); + + const selectedItems = pickerContext.getSelection(); + if (selectedItems.length === 0) return; + + const repository = config.repository(); + const { data } = await repository.requestByUnique(selectedItems[0]); + + if (!data) return; + + this.#openPropertyPicker(data, config.systemProperties); + } + + async #openPropertyPicker(contentType?: UmbContentTypeModel, systemProperties?: Array) { + if (!contentType) return; + if (!this.#modalManager) return; + + const properties: Array = + contentType?.properties.map((property) => ({ + label: property.name, + value: property.alias, + description: property.alias, + })) ?? []; + + const items = [...(systemProperties ?? []), ...properties]; + + const modalContext = this.#modalManager.open(this, UMB_ITEM_PICKER_MODAL, { + data: { + headline: `Select a property from ${contentType.name}`, + items: items, + }, + }); + + const modalValue = await modalContext.onSubmit(); + + if (!modalValue) return; + + this.selectedProperty = { + label: modalValue.label, + alias: modalValue.value, + isSystem: systemProperties?.some((property) => property.value === modalValue.value) ?? false, + }; + + this.dispatchEvent(new UmbChangeEvent()); + } + + render() { + return html``; + } + + static styles = [ + css` + :host { + display: flex; + flex-direction: column; + gap: var(--uui-size-space-1); + } + `, + ]; +} + +export default UmbInputContentTypePropertyElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-input-content-type-property': UmbInputContentTypePropertyElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/components/property-type-based-property/property-type-based-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/components/property-type-based-property/property-type-based-property.element.ts index 96896bf18b..5a569e0cbf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/components/property-type-based-property/property-type-based-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/components/property-type-based-property/property-type-based-property.element.ts @@ -44,6 +44,7 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement { // If there is no UI, we will look up the Property editor model to find the default UI alias: if (!this._propertyEditorUiAlias && dataType?.editorAlias) { //use 'dataType.editorAlias' to look up the extension in the registry: + // TODO: lets implement a way to observe once. [NL] this.observe( umbExtensionsRegistry.byTypeAndAlias('propertyEditorSchema', dataType.editorAlias), (extension) => { @@ -53,6 +54,8 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement { }, '_observePropertyEditorSchema', ); + } else { + this.removeControllerByAlias('_observePropertyEditorSchema'); } }, '_observeDataType', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/content-type-container-structure-helper.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/content-type-container-structure-helper.class.ts deleted file mode 100644 index b6ddbd5ac1..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/content-type-container-structure-helper.class.ts +++ /dev/null @@ -1,247 +0,0 @@ -import type { UmbContentTypePropertyStructureManager } from './content-type-structure-manager.class.js'; -import type { UmbContentTypeModel, UmbPropertyContainerTypes, UmbPropertyTypeContainerModel } from './types.js'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UmbArrayState, UmbBooleanState, UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; - -export class UmbContentTypeContainerStructureHelper { - #host: UmbControllerHost; - #init; - #initResolver?: (value: unknown) => void; - - #structure?: UmbContentTypePropertyStructureManager; - - private _ownerType?: UmbPropertyContainerTypes = 'Tab'; - private _childType?: UmbPropertyContainerTypes = 'Group'; - private _isRoot = false; - /** - * The owner id is the owning container (The container that is begin presented, the container is the parent of the child containers) - * If set to null, this helper class will provide containers of the root. - */ - private _ownerId?: string | null; - private _ownerName?: string; - - // Containers defined in data might be more than actual containers to display as we merge them by name. - // Direct containers are the containers defining the total of this container(Multiple containers with the same name and type) - private _ownerAlikeContainers: UmbPropertyTypeContainerModel[] = []; - // Owner containers are containers owned by the owner Content Type (The specific one up for editing) - private _ownerContainers: UmbPropertyTypeContainerModel[] = []; - - // State containing the merged containers (only one pr. name): - #containers = new UmbArrayState([], (x) => x.id); - readonly containers = this.#containers.asObservable(); - - #hasProperties = new UmbBooleanState(false); - readonly hasProperties = this.#hasProperties.asObservable(); - - constructor(host: UmbControllerHost) { - this.#host = host; - this.#init = new Promise((resolve) => { - this.#initResolver = resolve; - }); - - this.#containers.sortBy((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0)); - } - - public setStructureManager(structure: UmbContentTypePropertyStructureManager) { - this.#structure = structure; - this.#initResolver?.(undefined); - this.#initResolver = undefined; - this._observeOwnerAlikeContainers(); - } - - public setType(value?: UmbPropertyContainerTypes) { - if (this._ownerType === value) return; - this._ownerType = value; - this._observeOwnerAlikeContainers(); - } - public getType() { - return this._ownerType; - } - - public setContainerChildType(value?: UmbPropertyContainerTypes) { - if (this._childType === value) return; - this._childType = value; - this._observeOwnerAlikeContainers(); - } - public getContainerChildType() { - return this._childType; - } - - public setName(value?: string) { - if (this._ownerName === value) return; - this._ownerName = value; - this._observeOwnerAlikeContainers(); - } - public getName() { - return this._ownerName; - } - - public setIsRoot(value: boolean) { - if (this._isRoot === value) return; - this._isRoot = value; - this._observeOwnerAlikeContainers(); - } - public getIsRoot() { - return this._isRoot; - } - - public setOwnerId(value: string | null | undefined) { - if (this._ownerId === value) return; - this._ownerId = value; - } - public getOwnerId() { - return this._ownerId; - } - - private _observeOwnerAlikeContainers() { - if (!this.#structure || !this._ownerType) return; - - if (this._isRoot) { - this.#containers.setValue([]); - // We cannot have root properties currently, therefor we set it to false: - this.#hasProperties.setValue(false); - this._observeRootContainers(); - new UmbObserverController( - this.#host, - this.#structure.ownerContainersOf(this._ownerType), - (ownerContainers) => { - this._ownerContainers = ownerContainers || []; - }, - '_observeOwnerContainers', - ); - } else if (this._ownerName) { - new UmbObserverController( - this.#host, - this.#structure.containersByNameAndType(this._ownerName, this._ownerType), - (ownerALikeContainers) => { - this.#containers.setValue([]); - this._ownerContainers = ownerALikeContainers.filter((x) => x.id === this._ownerId) || []; - this._ownerAlikeContainers = ownerALikeContainers || []; - if (this._ownerAlikeContainers.length > 0) { - this._observeChildContainerProperties(); - this._observeChildContainers(); - } - }, - '_observeOwnerContainers', - ); - } - } - - private _observeChildContainerProperties() { - if (!this.#structure) return; - - this._ownerAlikeContainers.forEach((container) => { - new UmbObserverController( - this.#host, - this.#structure!.hasPropertyStructuresOf(container.id!), - (hasProperties) => { - this.#hasProperties.setValue(hasProperties); - }, - '_observeOwnerHasProperties_' + container.id, - ); - }); - } - - private _observeChildContainers() { - if (!this.#structure || !this._ownerName || !this._childType) return; - - this._ownerAlikeContainers.forEach((container) => { - new UmbObserverController( - this.#host, - this.#structure!.containersOfParentKey(container.id, this._childType!), - this._insertGroupContainers, - '_observeGroupsOf_' + container.id, - ); - }); - } - - private _observeRootContainers() { - if (!this.#structure || !this._isRoot) return; - - new UmbObserverController( - this.#host, - this.#structure.rootContainers(this._childType!), - (rootContainers) => { - this.#containers.setValue([]); - this._insertGroupContainers(rootContainers); - }, - '_observeRootContainers', - ); - } - - private _insertGroupContainers = (groupContainers: UmbPropertyTypeContainerModel[]) => { - groupContainers.forEach((group) => { - if (group.name !== null && group.name !== undefined) { - if (!this.#containers.getValue().find((x) => x.name === group.name)) { - this.#containers.appendOne(group); - } - } - }); - }; - - /** - * Returns true if the container is an owner container. - */ - isOwnerContainer(containerId?: string) { - if (!this.#structure || !containerId) return; - - return this._ownerContainers.find((x) => x.id === containerId) !== undefined; - } - - /** - * Returns true if the container is an owner container. - */ - isOwnerChildContainer(containerId?: string) { - if (!this.#structure || !containerId) return; - - return ( - this.#containers - .getValue() - .find((x) => (x.id === containerId && this._ownerId ? x.parent?.id === this._ownerId : x.parent === null)) !== - undefined - ); - } - - /** Manipulate methods: */ - - async insertContainer(container: UmbPropertyTypeContainerModel, sortOrder = 0) { - await this.#init; - if (!this.#structure) return false; - - const newContainer = { ...container, sortOrder }; - - await this.#structure.insertContainer(null, newContainer); - return true; - } - - async addContainer(parentContainerId?: string | null, sortOrder?: number) { - if (!this.#structure) return; - - await this.#structure.createContainer(null, parentContainerId, this._childType, sortOrder); - } - - async removeContainer(groupId: string) { - await this.#init; - if (!this.#structure) return false; - - await this.#structure.removeContainer(null, groupId); - return true; - } - - async partialUpdateContainer(containerId: string, partialUpdate: Partial) { - await this.#init; - if (!this.#structure || !containerId || !partialUpdate) return; - - return await this.#structure.updateContainer(null, containerId, partialUpdate); - } - - async updateContainerName(containerId: string, containerParentId: string | null, name: string) { - await this.#init; - if (!this.#structure) return; - - const newName = - this.#structure.makeContainerNameUniqueForOwnerContentType(name, this._childType, containerParentId) ?? name; - - return await this.partialUpdateContainer(containerId, { name: newName }); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/content-type-property-structure-helper.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/content-type-property-structure-helper.class.ts deleted file mode 100644 index 4f5f31441f..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/content-type-property-structure-helper.class.ts +++ /dev/null @@ -1,151 +0,0 @@ -import type { UmbContentTypePropertyStructureManager } from './content-type-structure-manager.class.js'; -import type { UmbContentTypeModel, UmbPropertyContainerTypes, UmbPropertyTypeModel } from './types.js'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UmbArrayState, UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; - -export class UmbContentTypePropertyStructureHelper { - #host: UmbControllerHost; - #init; - #initResolver?: (value: unknown) => void; - - #structure?: UmbContentTypePropertyStructureManager; - - private _containerType?: UmbPropertyContainerTypes; - private _isRoot?: boolean; - private _containerName?: string; - - #propertyStructure = new UmbArrayState([], (x) => x.id); - readonly propertyStructure = this.#propertyStructure.asObservable(); - - constructor(host: UmbControllerHost) { - this.#host = host; - this.#init = new Promise((resolve) => { - this.#initResolver = resolve; - }); - // TODO: Remove as any when sortOrder is implemented: - this.#propertyStructure.sortBy((a, b) => ((a as any).sortOrder ?? 0) - ((b as any).sortOrder ?? 0)); - } - - async ownerDocumentTypes() { - await this.#init; - if (!this.#structure) return; - return this.#structure.contentTypes; - } - - public setStructureManager(structure: UmbContentTypePropertyStructureManager) { - this.#structure = structure; - this.#initResolver?.(undefined); - this.#initResolver = undefined; - this._observeGroupContainers(); - } - - public setContainerType(value?: UmbPropertyContainerTypes) { - if (this._containerType === value) return; - this._containerType = value; - this._observeGroupContainers(); - } - public getContainerType() { - return this._containerType; - } - - public setContainerName(value?: string) { - if (this._containerName === value) return; - this._containerName = value; - this._observeGroupContainers(); - } - public getContainerName() { - return this._containerName; - } - - public setIsRoot(value: boolean) { - if (this._isRoot === value) return; - this._isRoot = value; - this._observeGroupContainers(); - } - public getIsRoot() { - return this._isRoot; - } - - private _observeGroupContainers() { - if (!this.#structure || !this._containerType) return; - - if (this._isRoot === true) { - this._observePropertyStructureOf(null); - } else if (this._containerName !== undefined) { - new UmbObserverController( - this.#host, - this.#structure.containersByNameAndType(this._containerName, this._containerType), - (groupContainers) => { - groupContainers.forEach((group) => this._observePropertyStructureOf(group.id)); - }, - '_observeGroupContainers', - ); - } - } - - private _observePropertyStructureOf(groupId?: string | null) { - if (!this.#structure || groupId === undefined) return; - - new UmbObserverController( - this.#host, - this.#structure.propertyStructuresOf(groupId), - (properties) => { - // If this need to be able to remove properties, we need to clean out the ones of this group.id before inserting them: - const _propertyStructure = this.#propertyStructure.getValue().filter((x) => x.container?.id !== groupId); - - properties?.forEach((property) => { - if (!_propertyStructure.find((x) => x.alias === property.alias)) { - _propertyStructure.push(property); - } - }); - - // Fire update to subscribers: - this.#propertyStructure.setValue(_propertyStructure); - }, - '_observePropertyStructureOfGroup' + groupId, - ); - } - - // TODO: consider moving this to another class, to separate 'viewer' from 'manipulator': - /** Manipulate methods: */ - - async createPropertyScaffold(ownerId?: string, sortOrder?: number) { - await this.#init; - if (!this.#structure) return; - - return await this.#structure.createPropertyScaffold(ownerId, sortOrder); - } - - async addProperty(ownerId?: string, sortOrder?: number) { - await this.#init; - if (!this.#structure) return; - - return await this.#structure.createProperty(null, ownerId, sortOrder); - } - - async insertProperty(property: UmbPropertyTypeModel, sortOrder = 0) { - await this.#init; - if (!this.#structure) return false; - - const newProperty = { ...property, sortOrder }; - - // TODO: Remove as any when server model has gotten sortOrder: - await this.#structure.insertProperty(null, newProperty); - return true; - } - - async removeProperty(propertyId: string) { - await this.#init; - if (!this.#structure) return false; - - await this.#structure.removeProperty(null, propertyId); - return true; - } - - // Takes optional arguments as this is easier for the implementation in the view: - async partialUpdateProperty(propertyKey?: string, partialUpdate?: Partial) { - await this.#init; - if (!this.#structure || !propertyKey || !partialUpdate) return; - return await this.#structure.updateProperty(null, propertyKey, partialUpdate); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/index.ts index f2eddad99c..6f9d7d26e5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/index.ts @@ -1,6 +1,6 @@ -export * from './content-type-container-structure-helper.class.js'; -export * from './content-type-property-structure-helper.class.js'; -export * from './content-type-structure-manager.class.js'; -export * from './types.js'; export * from './components/index.js'; +export * from './modals/index.js'; +export * from './repository/index.js'; export * from './structure/index.js'; +export * from './types.js'; +export * from './workspace/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/manifests.ts new file mode 100644 index 0000000000..824f90f7ad --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/manifests.ts @@ -0,0 +1,4 @@ +import { manifests as workspaceManifests } from './workspace/manifests.js'; +import { manifests as modalManifests } from './modals/manifests.js'; + +export const manifests = [...workspaceManifests, ...modalManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/modals/composition-picker/composition-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/composition-picker/composition-picker-modal.element.ts similarity index 86% rename from src/Umbraco.Web.UI.Client/src/packages/documents/document-types/modals/composition-picker/composition-picker-modal.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/composition-picker/composition-picker-modal.element.ts index 832ee49263..8a7ee4b324 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/modals/composition-picker/composition-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/composition-picker/composition-picker-modal.element.ts @@ -1,4 +1,3 @@ -import { UmbDocumentTypeCompositionRepository } from '../../repository/index.js'; import type { UmbCompositionPickerModalData, UmbCompositionPickerModalValue, @@ -6,9 +5,12 @@ import type { import { css, html, customElement, state, repeat, nothing } from '@umbraco-cms/backoffice/external/lit'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import type { + UmbDocumentTypeCompositionRepository, UmbDocumentTypeCompositionCompatibleModel, UmbDocumentTypeCompositionReferenceModel, } from '@umbraco-cms/backoffice/document-type'; +import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; interface CompatibleCompositions { path: string; @@ -20,8 +22,10 @@ export class UmbCompositionPickerModalElement extends UmbModalBaseElement< UmbCompositionPickerModalData, UmbCompositionPickerModalValue > { - #compositionRepository = new UmbDocumentTypeCompositionRepository(this); + // TODO: Loosen this from begin specific to Document Types, so we can have a general interface for composition repositories. [NL] + #compositionRepository?: UmbDocumentTypeCompositionRepository; #unique?: string; + #init?: Promise; @state() private _references: Array = []; @@ -31,10 +35,18 @@ export class UmbCompositionPickerModalElement extends UmbModalBaseElement< @state() private _selection: Array = []; - connectedCallback() { super.connectedCallback(); + const alias = this.data?.compositionRepositoryAlias; + if (alias) { + this.#init = new UmbExtensionApiInitializer(this, umbExtensionsRegistry, alias, [this], (permitted, ctrl) => { + this.#compositionRepository = permitted ? (ctrl.api as UmbDocumentTypeCompositionRepository) : undefined; + }).asPromise(); + } else { + throw new Error('No composition repository alias provided'); + } + this._selection = this.data?.selection ?? []; this.modalContext?.setValue({ selection: this._selection }); @@ -42,8 +54,9 @@ export class UmbCompositionPickerModalElement extends UmbModalBaseElement< } async #requestReference() { + await this.#init; this.#unique = this.data?.unique; - if (!this.#unique) return; + if (!this.#unique || !this.#compositionRepository) return; const { data } = await this.#compositionRepository.getReferences(this.#unique); @@ -55,7 +68,8 @@ export class UmbCompositionPickerModalElement extends UmbModalBaseElement< } async #requestAvailableCompositions() { - if (!this.#unique) return; + await this.#init; + if (!this.#unique || !this.#compositionRepository) return; const isElement = this.data?.isElement; const currentPropertyAliases = this.data?.currentPropertyAliases; @@ -76,6 +90,16 @@ export class UmbCompositionPickerModalElement extends UmbModalBaseElement< })); } + #onSelectionAdd(unique: string) { + this._selection = [...this._selection, unique]; + this.modalContext?.setValue({ selection: this._selection }); + } + + #onSelectionRemove(unique: string) { + this._selection = this._selection.filter((s) => s !== unique); + this.modalContext?.setValue({ selection: this._selection }); + } + render() { return html` @@ -146,16 +170,6 @@ export class UmbCompositionPickerModalElement extends UmbModalBaseElement< } } - #onSelectionAdd(unique: string) { - this._selection = [...this._selection, unique]; - this.modalContext?.setValue({ selection: this._selection }); - } - - #onSelectionRemove(unique: string) { - this._selection = this._selection.filter((s) => s !== unique); - this.modalContext?.setValue({ selection: this._selection }); - } - #renderCompositionsItems(compositionsList: Array) { return repeat( compositionsList, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/modals/composition-picker/composition-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/composition-picker/composition-picker-modal.token.ts similarity index 81% rename from src/Umbraco.Web.UI.Client/src/packages/documents/document-types/modals/composition-picker/composition-picker-modal.token.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/composition-picker/composition-picker-modal.token.ts index 7a006e6642..127dc22d24 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/modals/composition-picker/composition-picker-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/composition-picker/composition-picker-modal.token.ts @@ -1,6 +1,8 @@ import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; +// TODO: Stop sending the initial selection as part of data [NL], it should just be in the value: export interface UmbCompositionPickerModalData { + compositionRepositoryAlias: string; selection: Array; unique: string; //Do we really need to send this to the server - Why isn't unique enough? diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/modals/composition-picker/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/composition-picker/index.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/documents/document-types/modals/composition-picker/index.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/composition-picker/index.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/index.ts new file mode 100644 index 0000000000..15bf3de62f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/index.ts @@ -0,0 +1,2 @@ +export * from './composition-picker/composition-picker-modal.token.js'; +export * from './property-type-settings/property-type-settings-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/manifests.ts new file mode 100644 index 0000000000..daedade274 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/manifests.ts @@ -0,0 +1,18 @@ +import type { ManifestModal } from '@umbraco-cms/backoffice/extension-registry'; + +const modals: Array = [ + { + type: 'modal', + alias: 'Umb.Modal.CompositionPicker', + name: 'ContentType Composition Picker Modal', + js: () => import('./composition-picker/composition-picker-modal.element.js'), + }, + { + type: 'modal', + alias: 'Umb.Modal.PropertyTypeSettings', + name: 'Property Type Settings Modal', + js: () => import('./property-type-settings/property-type-settings-modal.element.js'), + }, +]; + +export const manifests = modals; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/property-settings/property-settings-modal.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/property-type-settings/property-type-settings-modal.context.ts similarity index 89% rename from src/Umbraco.Web.UI.Client/src/packages/core/modal/common/property-settings/property-settings-modal.context.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/property-type-settings/property-type-settings-modal.context.ts index 82d2f7bc4f..e22f3f0aa2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/property-settings/property-settings-modal.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/property-type-settings/property-type-settings-modal.context.ts @@ -6,7 +6,7 @@ import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; export const UMB_PROPERTY_TYPE_WORKSPACE_ALIAS = 'Umb.Workspace.PropertyType'; /** - * This is a very simplified workspace context, just to serve one for the imitated property type workspace. (As its not a real workspace) + * This is a very simplified workspace context, just to serve one for the imitated property type workspace. (As its not a real workspace, but this does as well provide the ability for extension-conditions to match with this workspace, as entity type and alias is available.) [NL] */ export class UmbPropertyTypeWorkspaceContext extends UmbContextBase diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/property-settings/property-settings-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/property-type-settings/property-type-settings-modal.element.ts similarity index 89% rename from src/Umbraco.Web.UI.Client/src/packages/core/modal/common/property-settings/property-settings-modal.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/property-type-settings/property-type-settings-modal.element.ts index 6e6e313c9c..c28a24bcd3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/property-settings/property-settings-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/modals/property-type-settings/property-type-settings-modal.element.ts @@ -1,23 +1,26 @@ import { UMB_PROPERTY_TYPE_WORKSPACE_ALIAS, UmbPropertyTypeWorkspaceContext, -} from './property-settings-modal.context.js'; -import { UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/document-type'; +} from './property-type-settings-modal.context.js'; +import type { + UmbPropertyTypeSettingsModalData, + UmbPropertyTypeSettingsModalValue, +} from './property-type-settings-modal.token.js'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UUIBooleanInputEvent, UUIInputEvent, UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; -import { css, html, nothing, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbPropertySettingsModalValue, UmbPropertySettingsModalData } from '@umbraco-cms/backoffice/modal'; +import { css, html, nothing, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import { generateAlias } from '@umbraco-cms/backoffice/utils'; -// TODO: Could base take a token to get its types?. -@customElement('umb-property-settings-modal') -export class UmbPropertySettingsModalElement extends UmbModalBaseElement< - UmbPropertySettingsModalData, - UmbPropertySettingsModalValue +import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/content-type'; +// TODO: Could base take a token to get its types? [NL] +@customElement('umb-property-type-settings-modal') +export class UmbPropertyTypeSettingsModalElement extends UmbModalBaseElement< + UmbPropertyTypeSettingsModalData, + UmbPropertyTypeSettingsModalValue > { - //TODO: Should these options come from the server? - // TODO: Or should they come from a extension point? + //TODO: Should these options come from the server? [NL] + // TODO: Or should they come from a extension point? [NL] @state() private _customValidationOptions: Array