From 433bdc4c74ef6a620cc9bb02d1a47e7cf9aed220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 24 Apr 2024 23:42:36 +0200 Subject: [PATCH] deep partial modal data, for more fine grained definitions --- .../modal/context/modal-manager.context.ts | 2 +- .../core/modal/context/modal.context.ts | 18 +++++++++-- .../modal-route-registration.controller.ts | 19 +++++++----- .../modal-route-registration.interface.ts | 5 +++- .../packages/core/modal/token/modal-token.ts | 10 +++++-- .../tree-picker/tree-picker-modal.token.ts | 4 +-- .../src/packages/core/utils/index.ts | 2 +- .../core/utils/object/deep-merge.function.ts | 11 ++++--- .../utils/type/deep-partial-object.type.ts | 30 +++++++++++++++++++ .../core/utils/type/deep-partial.type.ts | 5 ---- 10 files changed, 80 insertions(+), 26 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/utils/type/deep-partial-object.type.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/utils/type/deep-partial.type.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts index eb74e7deca..0764a232c8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts @@ -38,7 +38,7 @@ export class UmbModalManagerContext extends UmbContextBase, >( diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts index 05d6220b7a..2fc6c8da0c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts @@ -6,6 +6,7 @@ import type { UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui'; import { UmbId } from '@umbraco-cms/backoffice/id'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import { type UmbDeepPartialObject, umbDeepMerge } from '@umbraco-cms/backoffice/utils'; export interface UmbModalRejectReason { type: string; @@ -13,7 +14,9 @@ export interface UmbModalRejectReason { export type UmbModalContextClassArgs< ModalAliasType extends string | UmbModalToken, - ModalAliasTypeAsToken extends UmbModalToken = ModalAliasType extends UmbModalToken ? ModalAliasType : UmbModalToken, + ModalAliasTypeAsToken extends UmbModalToken = ModalAliasType extends UmbModalToken + ? ModalAliasType + : UmbModalToken, > = { router?: IRouterSlot | null; data?: ModalAliasTypeAsToken['DATA']; @@ -22,7 +25,10 @@ export type UmbModalContextClassArgs< }; // TODO: consider splitting this into two separate handlers -export class UmbModalContext extends UmbControllerBase { +export class UmbModalContext< + ModalPreset extends { [key: string]: any } = { [key: string]: any }, + ModalValue = any, +> extends UmbControllerBase { // #submitPromise: Promise; #submitResolver?: (value: ModalValue) => void; @@ -60,7 +66,13 @@ export class UmbModalContext, defaultData) as ModalPreset) + : // otherwise pick one of them: + (args.data as ModalPreset) ?? defaultData, + ); const initValue = args.value ?? (this.alias instanceof UmbModalToken ? (this.alias as UmbModalToken).getDefaultValue() : undefined); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.controller.ts index b4de970109..b645cc0185 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.controller.ts @@ -1,25 +1,30 @@ import type { UmbModalToken } from '../token/index.js'; import type { UmbModalConfig, UmbModalContext, UmbModalManagerContext, UmbModalRouteRegistration } from '../index.js'; +import type { UmbModalContextClassArgs } from '../context/modal.context.js'; import { type Params, type IRouterSlot, UMB_ROUTE_CONTEXT, encodeFolderName } from '@umbraco-cms/backoffice/router'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import { UmbId } from '@umbraco-cms/backoffice/id'; +import type { UmbDeepPartialObject } from '@umbraco-cms/backoffice/utils'; export type UmbModalRouteBuilder = (params: { [key: string]: string | number } | null) => string; export type UmbModalRouteSetupReturn = UmbModalTokenValue extends undefined ? { - modal?: UmbModalConfig; - data: UmbModalTokenData; + modal?: UmbDeepPartialObject; + data: UmbDeepPartialObject; value?: UmbModalTokenValue; } : { - modal?: UmbModalConfig; - data: UmbModalTokenData; + modal?: UmbDeepPartialObject; + data: UmbDeepPartialObject; value: UmbModalTokenValue; }; -export class UmbModalRouteRegistrationController +export class UmbModalRouteRegistrationController< + UmbModalTokenData extends { [key: string]: any } = { [key: string]: any }, + UmbModalTokenValue = any, + > extends UmbControllerBase implements UmbModalRouteRegistration { @@ -293,8 +298,8 @@ export class UmbModalRouteRegistrationController>; + args.modal!.key = this.#key; this.#modalContext = modalManagerContext.open(this, this.#modalAlias, args); this.#modalContext.onSubmit().then(this.#onSubmit, this.#onReject); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.interface.ts index e4113c2408..a1af2fbcf7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/route-registration/modal-route-registration.interface.ts @@ -3,7 +3,10 @@ import type { UmbModalContext, UmbModalRouteBuilder } from '../index.js'; import type { UmbModalToken } from '../token/modal-token.js'; import type { IRouterSlot, Params } from '@umbraco-cms/backoffice/router'; -export interface UmbModalRouteRegistration { +export interface UmbModalRouteRegistration< + UmbModalTokenData extends { [key: string]: any } = { [key: string]: any }, + UmbModalTokenValue = any, +> { key: string; alias: UmbModalToken | string; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/modal-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/modal-token.ts index 8d45fde4b9..61d66ce63e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/modal-token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/modal-token.ts @@ -1,12 +1,18 @@ import type { UmbModalConfig } from '../context/modal-manager.context.js'; -export interface UmbModalTokenDefaults { +export interface UmbModalTokenDefaults< + ModalDataType extends { [key: string]: any } = { [key: string]: any }, + ModalValueType = unknown, +> { modal?: UmbModalConfig; data?: ModalDataType; value?: ModalValueType; } -export class UmbModalToken { +export class UmbModalToken< + ModalDataType extends { [key: string]: any } = { [key: string]: any }, + ModalValueType = unknown, +> { /** * Get the data type of the token's data. * diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker/tree-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker/tree-picker-modal.token.ts index a6ab0af664..d5d3affed0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker/tree-picker-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-picker/tree-picker-modal.token.ts @@ -11,7 +11,7 @@ export interface UmbTreePickerModalCreateActionData extends UmbPickerModalData { treeAlias?: string; @@ -21,7 +21,7 @@ export interface UmbTreePickerModalData< export interface UmbTreePickerModalValue extends UmbPickerModalValue {} -export const UMB_TREE_PICKER_MODAL = new UmbModalToken( +export const UMB_TREE_PICKER_MODAL = new UmbModalToken, UmbTreePickerModalValue>( UMB_TREE_PICKER_MODAL_ALIAS, { modal: { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts index 572569ab13..6895540e28 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts @@ -17,5 +17,5 @@ export * from './string/generate-umbraco-alias.function.js'; export * from './string/increment-string.function.js'; export * from './string/split-string-to-array.js'; export * from './string/to-camel-case/to-camel-case.function.js'; -export type * from './type/deep-partial.type.js'; +export type * from './type/deep-partial-object.type.js'; export type * from './type/diff.type.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/object/deep-merge.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/object/deep-merge.function.ts index b08629cb1d..94b67c9b94 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/object/deep-merge.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/object/deep-merge.function.ts @@ -1,17 +1,20 @@ -import type { DeepPartial } from '../type/deep-partial.type.js'; +import type { UmbDeepPartialObject } from '../type/deep-partial-object.type.js'; /** * Deep merge two objects. * @param target * @param ...sources */ -export function umbDeepMerge(source: DeepPartial, fallback: T) { +export function umbDeepMerge< + T extends { [key: string]: any }, + PartialType extends UmbDeepPartialObject = UmbDeepPartialObject, +>(source: PartialType, fallback: T) { const result = { ...fallback }; for (const key in source) { if (Object.prototype.hasOwnProperty.call(source, key) && source[key] !== undefined) { - if (source[key].constructor === Object && fallback[key].constructor === Object) { - result[key] = umbDeepMerge(source[key], fallback[key]); + if (source[key]?.constructor === Object && fallback[key].constructor === Object) { + result[key] = umbDeepMerge(source[key] as any, fallback[key]); } else { result[key] = source[key] as any; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/deep-partial-object.type.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/deep-partial-object.type.ts new file mode 100644 index 0000000000..7961caf5d1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/deep-partial-object.type.ts @@ -0,0 +1,30 @@ +/*export type DeepPartial = T extends { [key: string]: any } + ? { + [P in keyof T]?: DeepPartial; + } + : T; +*/ + +// Notice this can be way more complex, but in this case I just wanted to cover pure objects, to match our deep merge function [NL] +// See https://stackoverflow.com/questions/61132262/typescript-deep-partial for more extensive solutions. +/** + * Deep partial object type, making objects and their properties optional, but only until a property of a different type is encountered. + * This means if an object holds a property with an array that holds objects, the array will be made optional, but the properties of the objects inside the array will not be changed. + * @type UmbDeepPartialObject + * @generic T - The object to make partial. + * @returns A type with all properties of objects made optional. + */ +// eslint-disable-next-line @typescript-eslint/ban-types +export type UmbDeepPartialObject = T extends Function + ? T + : // Thing extends Array + // ? DeepPartialArray : + T extends { [key: string]: any } + ? UmbDeepPartialObjectProperty + : T | undefined; + +//interface DeepPartialArray extends Array> {} + +type UmbDeepPartialObjectProperty = { + [Key in keyof Thing]?: UmbDeepPartialObject; +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/deep-partial.type.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/deep-partial.type.ts deleted file mode 100644 index 65bb71bfa0..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/deep-partial.type.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type DeepPartial = T extends object - ? { - [P in keyof T]?: DeepPartial; - } - : T;