diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DataTypeResponseModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DataTypeResponseModel.ts index 5fa2a1085c..ed9934a1b5 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DataTypeResponseModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DataTypeResponseModel.ts @@ -4,9 +4,8 @@ import type { DataTypeModelBaseModel } from './DataTypeModelBaseModel'; -export type DataTypeResponseModel = (DataTypeModelBaseModel & { - $type: string; - id?: string; - parentId?: string | null; -}); - +export type DataTypeResponseModel = DataTypeModelBaseModel & { + $type: string; + id?: string; + parentId?: string | null; +}; diff --git a/src/Umbraco.Web.UI.Client/libs/repository/data-source/data-source.interface.ts b/src/Umbraco.Web.UI.Client/libs/repository/data-source/data-source.interface.ts index 0a6d922702..1721ce3e47 100644 --- a/src/Umbraco.Web.UI.Client/libs/repository/data-source/data-source.interface.ts +++ b/src/Umbraco.Web.UI.Client/libs/repository/data-source/data-source.interface.ts @@ -1,7 +1,7 @@ import type { DataSourceResponse } from '@umbraco-cms/backoffice/repository'; export interface UmbDataSource { - createScaffold(parentId: string | null): Promise>; + createScaffold(parentId: string | null): Promise>; get(unique: string): Promise>; insert(data: CreateRequestType): Promise; update(unique: string, data: UpdateRequestType): Promise>; diff --git a/src/Umbraco.Web.UI.Client/libs/repository/detail-repository.interface.ts b/src/Umbraco.Web.UI.Client/libs/repository/detail-repository.interface.ts index 25a3cd332b..cb70ab8dd2 100644 --- a/src/Umbraco.Web.UI.Client/libs/repository/detail-repository.interface.ts +++ b/src/Umbraco.Web.UI.Client/libs/repository/detail-repository.interface.ts @@ -1,25 +1,16 @@ import type { ProblemDetailsModel } from '@umbraco-cms/backoffice/backend-api'; -export interface UmbDetailRepository { - createScaffold(parentId: string | null): Promise<{ - data?: DetailType; - error?: ProblemDetailsModel; - }>; - - requestById(id: string): Promise<{ - data?: DetailType; - error?: ProblemDetailsModel; - }>; - - create(data: DetailType): Promise<{ - error?: ProblemDetailsModel; - }>; - - save(data: DetailType): Promise<{ - error?: ProblemDetailsModel; - }>; - - delete(id: string): Promise<{ - error?: ProblemDetailsModel; - }>; +export interface UmbRepositoryErrorResponse { + error?: ProblemDetailsModel; +} +export interface UmbRepositoryResponse extends UmbRepositoryErrorResponse { + data?: T; +} + +export interface UmbDetailRepository { + createScaffold(parentId: string | null): Promise>; + requestById(id: string): Promise>; + create(data: CreateRequestType): Promise; + save(id: string, data: UpdateRequestType): Promise; + delete(id: string): Promise; } diff --git a/src/Umbraco.Web.UI.Client/libs/workspace/actions/save/save.action.ts b/src/Umbraco.Web.UI.Client/libs/workspace/actions/save/save.action.ts index 9cc3d5089c..032c166dee 100644 --- a/src/Umbraco.Web.UI.Client/libs/workspace/actions/save/save.action.ts +++ b/src/Umbraco.Web.UI.Client/libs/workspace/actions/save/save.action.ts @@ -18,6 +18,30 @@ export class UmbSaveWorkspaceAction extends UmbWorkspaceActionBase { destroy(): void; // TODO: temp solution to bubble validation errors to the UI setValidationErrors?(errorMap: any): void; + save(): void; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/document-type.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/document-type.repository.ts index dbf7c841e2..78c9ac203c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/document-type.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/document-type.repository.ts @@ -160,22 +160,24 @@ export class UmbDocumentTypeRepository implements UmbTreeRepository, U return { error }; } - async save(item: ItemType) { - if (!item || !item.id) throw new Error('Document-Type is missing'); + async save(id: string, item: any) { + if (!id) throw new Error('Id is missing'); + if (!item) throw new Error('Item is missing'); + await this.#init; - const { error } = await this.#detailDataSource.update(item.id, item); + const { error } = await this.#detailDataSource.update(id, item); if (!error) { - const notification = { data: { message: `Document saved` } }; - this.#notificationContext?.peek('positive', notification); - // TODO: we currently don't use the detail store for anything. // Consider to look up the data before fetching from the server // Consider notify a workspace if a template is updated in the store while someone is editing it. this.#detailStore?.append(item); - this.#treeStore?.updateItem(item.id, { name: item.name }); + this.#treeStore?.updateItem(id, item); // TODO: would be nice to align the stores on methods/methodNames. + + const notification = { data: { message: `Document Type saved` } }; + this.#notificationContext?.peek('positive', notification); } return { error }; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.context.ts index da264b5ff0..4ee41fc2b7 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.context.ts @@ -105,7 +105,9 @@ export class UmbDocumentTypeWorkspaceContext } async save() { - this.repository.save(this.getData()); + const id = this.getEntityId(); + if (!id) throw new Error('Cannot save entity without id'); + this.repository.save(id, this.getData()); } public destroy(): void { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.repository.ts index cdeea13e32..91d485f05c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/document.repository.ts @@ -9,6 +9,7 @@ import { ProblemDetailsModel, DocumentResponseModel, CreateDocumentRequestModel, + UpdateDocumentRequestModel, } from '@umbraco-cms/backoffice/backend-api'; import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification'; @@ -136,7 +137,6 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDe } // Could potentially be general methods: - async create(item: CreateDocumentRequestModel & { id: string }) { await this.#init; @@ -159,53 +159,49 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDe return { error }; } - async save(item: ItemType) { + async save(id: string, item: UpdateDocumentRequestModel) { + if (!id) throw new Error('Id is missing'); + if (!item) throw new Error('Item is missing'); + await this.#init; - if (!item || !item.id) { - throw new Error('Document is missing'); - } - - const { error } = await this.#detailDataSource.update(item.id, item); + const { error } = await this.#detailDataSource.update(id, item); if (!error) { + // TODO: we currently don't use the detail store for anything. + // Consider to look up the data before fetching from the server + // Consider notify a workspace if a document is updated in the store while someone is editing it. + this.#store?.append(item); + //this.#treeStore?.updateItem(item.id, { name: item.name });// Port data to tree store. + // TODO: would be nice to align the stores on methods/methodNames. + const notification = { data: { message: `Document saved` } }; this.#notificationContext?.peek('positive', notification); } - // TODO: we currently don't use the detail store for anything. - // Consider to look up the data before fetching from the server - // Consider notify a workspace if a document is updated in the store while someone is editing it. - this.#store?.append(item); - //this.#treeStore?.updateItem(item.id, { name: item.name });// Port data to tree store. - // TODO: would be nice to align the stores on methods/methodNames. - return { error }; } // General: async delete(id: string) { + if (!id) throw new Error('Id is missing'); await this.#init; - if (!id) { - throw new Error('Document id is missing'); - } - const { error } = await this.#detailDataSource.delete(id); if (!error) { + // TODO: we currently don't use the detail store for anything. + // Consider to look up the data before fetching from the server. + // Consider notify a workspace if a document is deleted from the store while someone is editing it. + // TODO: would be nice to align the stores on methods/methodNames. + this.#store?.remove([id]); + this.#treeStore?.removeItem(id); + const notification = { data: { message: `Document deleted` } }; this.#notificationContext?.peek('positive', notification); } - // TODO: we currently don't use the detail store for anything. - // Consider to look up the data before fetching from the server. - // Consider notify a workspace if a document is deleted from the store while someone is editing it. - this.#store?.remove([id]); - this.#treeStore?.removeItem(id); - // TODO: would be nice to align the stores on methods/methodNames. - return { error }; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts index 8e6816b0e9..e4d3f0b984 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.context.ts @@ -163,12 +163,14 @@ export class UmbDocumentWorkspaceContext async save() { if (!this.#draft.value) return; + if (!this.#draft.value.id) return; + if (this.getIsNew()) { // TODO: typescript hack until we get the create type const value = this.#draft.value as CreateDocumentRequestModel & { id: string }; await this.repository.create(value); } else { - await this.repository.save(this.#draft.value); + await this.repository.save(this.#draft.value.id, this.#draft.value); } // If it went well, then its not new anymore?. this.setIsNew(false); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.repository.ts index 53ee05be00..28e2aed79d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/media.repository.ts @@ -6,13 +6,19 @@ import { UmbMediaDetailServerDataSource } from './sources/media.detail.server.da import type { UmbTreeRepository, UmbTreeDataSource } from '@umbraco-cms/backoffice/repository'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; -import { ProblemDetailsModel } from '@umbraco-cms/backoffice/backend-api'; +import { + CreateMediaRequestModel, + ProblemDetailsModel, + UpdateMediaRequestModel, +} from '@umbraco-cms/backoffice/backend-api'; import { UmbDetailRepository } from '@umbraco-cms/backoffice/repository'; import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification'; type ItemDetailType = MediaDetails; -export class UmbMediaRepository implements UmbTreeRepository, UmbDetailRepository { +export class UmbMediaRepository + implements UmbTreeRepository, UmbDetailRepository +{ #host: UmbControllerHostElement; #treeSource: UmbTreeDataSource; @@ -121,24 +127,15 @@ export class UmbMediaRepository implements UmbTreeRepository, UmbDetailRepositor // DETAILS: async createScaffold(parentId: string | null) { + if (!parentId) throw new Error('Parent id is missing'); await this.#init; - - if (!parentId) { - throw new Error('Parent id is missing'); - } - return this.#detailDataSource.createScaffold(parentId); } async requestById(id: string) { + if (!id) throw new Error('Id is missing'); await this.#init; - // TODO: should we show a notification if the id is missing? - // Investigate what is best for Acceptance testing, cause in that perspective a thrown error might be the best choice? - if (!id) { - const error: ProblemDetailsModel = { title: 'Key is missing' }; - return { error }; - } const { data, error } = await this.#detailDataSource.get(id); if (data) { @@ -150,50 +147,46 @@ export class UmbMediaRepository implements UmbTreeRepository, UmbDetailRepositor // Could potentially be general methods: - async create(template: ItemDetailType) { + async create(media: CreateMediaRequestModel) { + if (!media) throw new Error('Media is missing'); + await this.#init; - if (!template || !template.id) { - throw new Error('Template is missing'); - } - - const { error } = await this.#detailDataSource.insert(template); + const { error } = await this.#detailDataSource.insert(media); if (!error) { + // TODO: we currently don't use the detail store for anything. + // Consider to look up the data before fetching from the server + // TODO: Update tree store with the new item? or ask tree to request the new item? + //this.#store?.append(media); + const notification = { data: { message: `Media created` } }; this.#notificationContext?.peek('positive', notification); } - // TODO: we currently don't use the detail store for anything. - // Consider to look up the data before fetching from the server - this.#store?.append(template); - // TODO: Update tree store with the new item? or ask tree to request the new item? - return { error }; } - async save(document: ItemDetailType) { + async save(id: string, updatedItem: UpdateMediaRequestModel) { + if (!id) throw new Error('Id is missing'); + if (!updatedItem) throw new Error('Updated media item is missing'); + await this.#init; - if (!document || !document.id) { - throw new Error('Template is missing'); - } - - const { error } = await this.#detailDataSource.update(document.id, document); + const { error } = await this.#detailDataSource.update(id, updatedItem); if (!error) { - const notification = { data: { message: `Document saved` } }; + // TODO: we currently don't use the detail store for anything. + // Consider to look up the data before fetching from the server + // Consider notify a workspace if a template is updated in the store while someone is editing it. + // TODO: would be nice to align the stores on methods/methodNames. + // this.#store?.append(updatedMediaItem); + // this.#treeStore?.updateItem(id, updatedItem); + + const notification = { data: { message: `Media saved` } }; this.#notificationContext?.peek('positive', notification); } - // TODO: we currently don't use the detail store for anything. - // Consider to look up the data before fetching from the server - // Consider notify a workspace if a template is updated in the store while someone is editing it. - this.#store?.append(document); - this.#treeStore?.updateItem(document.id, { name: document.name }); - - // TODO: would be nice to align the stores on methods/methodNames. - return { error }; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/sources/media.detail.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/sources/media.detail.server.data.ts index 1621d5628c..797329b01a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/sources/media.detail.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/sources/media.detail.server.data.ts @@ -1,6 +1,10 @@ import type { MediaDetails } from '../../'; import { UmbDataSource } from '@umbraco-cms/backoffice/repository'; -import { ProblemDetailsModel } from '@umbraco-cms/backoffice/backend-api'; +import { + CreateMediaRequestModel, + ProblemDetailsModel, + UpdateMediaRequestModel, +} from '@umbraco-cms/backoffice/backend-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; @@ -10,7 +14,9 @@ import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; * @class UmbTemplateDetailServerDataSource * @implements {TemplateDetailDataSource} */ -export class UmbMediaDetailServerDataSource implements UmbDataSource { +export class UmbMediaDetailServerDataSource + implements UmbDataSource +{ #host: UmbControllerHostElement; /** @@ -50,34 +56,15 @@ export class UmbMediaDetailServerDataSource implements UmbDataSource Update type when backend updated -export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRepository { +export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRepository { #init!: Promise; #host: UmbControllerHostElement; @@ -129,49 +129,44 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep return { data, error }; } - async save(memberGroup: MemberGroupDetails) { + async save(id: string, memberGroup: MemberGroupDetails) { + if (!id) throw new Error('Id is missing'); + if (!memberGroup) throw new Error('Member group is missing'); + await this.#init; - if (!memberGroup || !memberGroup.name) { - const error: ProblemDetailsModel = { title: 'Member group is missing' }; - return { error }; - } - - const { error } = await this.#detailSource.update(memberGroup.id, memberGroup); + const { error } = await this.#detailSource.update(id, memberGroup); if (!error) { + this.#store?.append(memberGroup); + this.#treeStore?.updateItem(memberGroup.id, memberGroup); + const notification = { data: { message: `Member group '${memberGroup.name} saved` } }; this.#notificationContext?.peek('positive', notification); } - this.#store?.append(memberGroup); - this.#treeStore?.updateItem(memberGroup.id, { name: memberGroup.name }); - return { error }; } async delete(id: string) { - await this.#init; + if (!id) throw new Error('Id is missing'); - if (!id) { - const error: ProblemDetailsModel = { title: 'Id is missing' }; - return { error }; - } + await this.#init; const { error } = await this.#detailSource.delete(id); if (!error) { + // TODO: we currently don't use the detail store for anything. + // Consider to look up the data before fetching from the server. + // Consider notify a workspace if a template is deleted from the store while someone is editing it. + // TODO: would be nice to align the stores on methods/methodNames. + this.#store?.remove([id]); + this.#treeStore?.removeItem(id); + const notification = { data: { message: `Document deleted` } }; this.#notificationContext?.peek('positive', notification); } - // TODO: we currently don't use the detail store for anything. - // Consider to look up the data before fetching from the server. - // Consider notify a workspace if a template is deleted from the store while someone is editing it. - this.#store?.remove([id]); - this.#treeStore?.removeItem(id); - // TODO: would be nice to align the stores on methods/methodNames. - return { error }; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/member-group-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/member-group-workspace.context.ts index 1687c7e290..ecf64222eb 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/member-group-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/workspace/member-group-workspace.context.ts @@ -56,7 +56,7 @@ export class UmbWorkspaceMemberGroupContext async save() { if (!this.#data.value) return; - await this.repository.save(this.#data.value); + await this.repository.save(this.#data.value.id, this.#data.value); this.setIsNew(true); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.repository.ts index 2226ee91ba..620347e0b4 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.repository.ts @@ -154,30 +154,26 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, return { error }; } - async save(detail: ItemType) { + async save(id: string, updatedMemberType: any) { + if (!id) throw new Error('Key is missing'); + if (!updatedMemberType) throw new Error('Member Type is missing'); + await this.#init; - // TODO: should we show a notification if the MemberType is missing? - // Investigate what is best for Acceptance testing, cause in that perspective a thrown error might be the best choice? - if (!detail || !detail.id) { - const error: ProblemDetailsModel = { title: 'Member type is missing' }; - return { error }; - } - - const { error } = await this.#detailSource.save(detail); + const { error } = await this.#detailSource.save(id, updatedMemberType); if (!error) { - const notification = { data: { message: `Member type '${detail.name}' saved` } }; + // TODO: we currently don't use the detail store for anything. + // Consider to look up the data before fetching from the server + // Consider notify a workspace if a member type is updated in the store while someone is editing it. + // TODO: would be nice to align the stores on methods/methodNames. + //this.#store?.append(detail); + this.#treeStore?.updateItem(id, updatedMemberType); + + const notification = { data: { message: `Member type '${updatedMemberType.name}' saved` } }; this.#notificationContext?.peek('positive', notification); } - // TODO: we currently don't use the detail store for anything. - // Consider to look up the data before fetching from the server - // Consider notify a workspace if a member type is updated in the store while someone is editing it. - this.#store?.append(detail); - this.#treeStore?.updateItem(detail.id, { name: detail.name }); - // TODO: would be nice to align the stores on methods/methodNames. - return { error }; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/sources/member-type.detail.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/sources/member-type.detail.server.data.ts index a5cb2f1c9e..ab8466c03c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/sources/member-type.detail.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/sources/member-type.detail.server.data.ts @@ -10,7 +10,7 @@ import { UmbDetailRepository } from '@umbraco-cms/backoffice/repository'; * @class UmbMemberTypeDetailServerDataSource * @implements {MemberTypeDetailDataSource} */ -export class UmbMemberTypeDetailServerDataSource implements UmbDetailRepository { +export class UmbMemberTypeDetailServerDataSource implements UmbDetailRepository { #host: UmbControllerHostElement; constructor(host: UmbControllerHostElement) { @@ -45,11 +45,8 @@ export class UmbMemberTypeDetailServerDataSource implements UmbDetailRepository< * @return {*} * @memberof UmbMemberTypeDetailServerDataSource */ - async save(memberType: MemberTypeDetails) { - if (!memberType.id) { - const error: ProblemDetailsModel = { title: 'MemberType id is missing' }; - return { error }; - } + async save(id: string, memberType: any) { + if (!id) throw new Error('Member Type id is missing'); const payload = { id: memberType.id, requestBody: memberType }; //return tryExecuteAndNotify(this.#host, MemberTypeResource.putMemberTypeByKey(payload)); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.context.ts index a7a0fcc644..8274bec279 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.context.ts @@ -55,10 +55,12 @@ export class UmbMemberTypeWorkspaceContext async save() { if (!this.#data.value) return; + if (!this.#data.value.id) return; + if (this.isNew) { await this.repository.create(this.#data.value); } else { - await this.repository.save(this.#data.value); + await this.repository.save(this.#data.value.id, this.#data.value); } // If it went well, then its not new anymore?. this.setIsNew(false); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/create/create.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/create/create.action.ts index 5849726dd0..bf87193435 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/create/create.action.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/create/create.action.ts @@ -1,5 +1,5 @@ import { UmbDataTypeRepository } from '../../repository/data-type.repository'; -import { UMB_CREATE_DATA_TYPE_MODAL } from './modal'; +import { UMB_DATA_TYPE_CREATE_OPTIONS_MODAL } from './modal'; import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/modal'; @@ -17,9 +17,11 @@ export class UmbCreateDataTypeEntityAction extends UmbEntityActionBase = [ type: 'entityAction', alias: 'Umb.EntityAction.DataType.Create', name: 'Create Data Type Entity Action', - weight: 900, + weight: 1000, meta: { icon: 'umb:add', label: 'Create', @@ -15,14 +15,14 @@ const entityActions: Array = [ api: UmbCreateDataTypeEntityAction, }, conditions: { - entityType: 'data-type-root', + entityType: 'data-type', }, }, { type: 'modal', - alias: 'Umb.Modal.CreateDataType', - name: 'Create Data Type Modal', - loader: () => import('./modal/create-data-type-modal.element'), + alias: 'Umb.Modal.DataTypeCreateOptions', + name: 'Data Type Create Options Modal', + loader: () => import('./modal/data-type-create-options-modal.element'), }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/create/modal/create-data-type-modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/create/modal/data-type-create-options-modal.element.ts similarity index 53% rename from src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/create/modal/create-data-type-modal.element.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/create/modal/data-type-create-options-modal.element.ts index cfe48669f7..f4c615847c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/create/modal/create-data-type-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/create/modal/data-type-create-options-modal.element.ts @@ -1,14 +1,26 @@ import { html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { customElement } from 'lit/decorators.js'; +import { customElement, property } from 'lit/decorators.js'; import { DATA_TYPE_REPOSITORY_ALIAS } from '../../../repository/manifests'; -import { UmbModalBaseElement } from '@umbraco-cms/internal/modal'; -import { UmbModalContext, UMB_FOLDER_MODAL, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/modal'; +import { UmbDataTypeCreateOptionsModalData } from '.'; +import { + UmbModalContext, + UmbModalHandler, + UMB_FOLDER_MODAL, + UMB_MODAL_CONTEXT_TOKEN, +} from '@umbraco-cms/backoffice/modal'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -@customElement('umb-create-data-type-modal') -export class UmbCreateDataTypeModalElement extends UmbModalBaseElement { +@customElement('umb-data-type-create-options-modal') +export class UmbDataTypeCreateOptionsModalElement extends UmbLitElement { static styles = [UUITextStyles]; + @property({ attribute: false }) + modalHandler?: UmbModalHandler; + + @property({ type: Object }) + data?: UmbDataTypeCreateOptionsModalData; + #modalContext?: UmbModalContext; constructor() { @@ -26,6 +38,11 @@ export class UmbCreateDataTypeModalElement extends UmbModalBaseElement { folderModalHandler?.onSubmit().then(() => this.modalHandler?.submit()); } + // close the modal when navigating to data type + #onNavigate() { + this.modalHandler?.submit(); + } + #onCancel() { this.modalHandler?.reject(); } @@ -34,7 +51,11 @@ export class UmbCreateDataTypeModalElement extends UmbModalBaseElement { return html` - + + } @@ -47,10 +68,10 @@ export class UmbCreateDataTypeModalElement extends UmbModalBaseElement { } } -export default UmbCreateDataTypeModalElement; +export default UmbDataTypeCreateOptionsModalElement; declare global { interface HTMLElementTagNameMap { - 'umb-create-data-type-modal': UmbCreateDataTypeModalElement; + 'umb-data-type-create-options-modal': UmbDataTypeCreateOptionsModalElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/create/modal/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/create/modal/index.ts index 9e1e8eb025..c6565fb240 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/create/modal/index.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/entity-actions/create/modal/index.ts @@ -1,6 +1,13 @@ import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; -export const UMB_CREATE_DATA_TYPE_MODAL = new UmbModalToken('Umb.Modal.CreateDataType', { - type: 'sidebar', - size: 'small', -}); +export interface UmbDataTypeCreateOptionsModalData { + parentKey: string | null; +} + +export const UMB_DATA_TYPE_CREATE_OPTIONS_MODAL = new UmbModalToken( + 'Umb.Modal.DataTypeCreateOptions', + { + type: 'sidebar', + size: 'small', + } +); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/menu-item/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/menu-item/manifests.ts index 1f44f2d881..29c995c92a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/menu-item/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/menu-item/manifests.ts @@ -9,7 +9,7 @@ const menuItem: ManifestTypes = { meta: { label: 'Data Types', icon: 'umb:folder', - entityType: 'data-type-root', + entityType: 'data-type', treeAlias: 'Umb.Tree.DataTypes', }, conditions: { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type.repository.ts index 8f93aa9f09..21a4d02041 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/data-type.repository.ts @@ -23,11 +23,11 @@ import { import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification'; import { UmbFolderRepository } from '@umbraco-cms/backoffice/repository'; -type ItemType = DataTypeResponseModel; -type TreeItemType = any; - export class UmbDataTypeRepository - implements UmbTreeRepository, UmbDetailRepository, UmbFolderRepository + implements + UmbTreeRepository, + UmbDetailRepository, + UmbFolderRepository { #init!: Promise; @@ -143,8 +143,7 @@ export class UmbDataTypeRepository return this.#detailStore!.byId(id); } - // Could potentially be general methods: - async create(dataType: ItemType) { + async create(dataType: CreateDataTypeRequestModel) { if (!dataType) throw new Error('Data Type is missing'); if (!dataType.id) throw new Error('Data Type id is missing'); @@ -153,36 +152,38 @@ export class UmbDataTypeRepository const { error } = await this.#detailSource.insert(dataType); if (!error) { + // TODO: We need to push a new item to the tree store to update the tree. How do we want to create the tree items? + const treeItem = createTreeItem(dataType); + this.#treeStore?.appendItems([treeItem]); + //this.#detailStore?.append(dataType); + const notification = { data: { message: `Data Type created` } }; this.#notificationContext?.peek('positive', notification); - - this.#detailStore?.append(dataType); - this.#treeStore?.appendItems([dataType]); } return { error }; } - async save(dataType: ItemType) { - if (!dataType) throw new Error('Data Type is missing'); - if (!dataType.id) throw new Error('Data Type id is missing'); + async save(id: string, updatedDataType: UpdateDataTypeRequestModel) { + if (!id) throw new Error('Data Type id is missing'); + if (!updatedDataType) throw new Error('Data Type is missing'); await this.#init; - const { error } = await this.#detailSource.update(dataType.id, dataType); + const { error } = await this.#detailSource.update(id, updatedDataType); if (!error) { + // TODO: we currently don't use the detail store for anything. + // Consider to look up the data before fetching from the server + // Consider notify a workspace if a template is updated in the store while someone is editing it. + // TODO: would be nice to align the stores on methods/methodNames. + // this.#detailStore?.append(dataType); + this.#treeStore?.updateItem(id, updatedDataType); + const notification = { data: { message: `Data Type saved` } }; this.#notificationContext?.peek('positive', notification); } - // TODO: we currently don't use the detail store for anything. - // Consider to look up the data before fetching from the server - // Consider notify a workspace if a template is updated in the store while someone is editing it. - this.#detailStore?.append(dataType); - this.#treeStore?.updateItem(dataType.id, { name: dataType.name }); - // TODO: would be nice to align the stores on methods/methodNames. - return { error }; } @@ -195,17 +196,17 @@ export class UmbDataTypeRepository const { error } = await this.#detailSource.delete(id); if (!error) { + // TODO: we currently don't use the detail store for anything. + // Consider to look up the data before fetching from the server. + // Consider notify a workspace if a template is deleted from the store while someone is editing it. + // TODO: would be nice to align the stores on methods/methodNames. + this.#detailStore?.remove([id]); + this.#treeStore?.removeItem(id); + const notification = { data: { message: `Data Type deleted` } }; this.#notificationContext?.peek('positive', notification); } - // TODO: we currently don't use the detail store for anything. - // Consider to look up the data before fetching from the server. - // Consider notify a workspace if a template is deleted from the store while someone is editing it. - this.#detailStore?.remove([id]); - this.#treeStore?.removeItem(id); - // TODO: would be nice to align the stores on methods/methodNames. - return { error }; } @@ -222,21 +223,10 @@ export class UmbDataTypeRepository const { error } = await this.#folderSource.insert(folderRequest); - // TODO: We need to push a new item to the tree store to update the tree. How do we want to create the tree items? if (!error) { - const treeItem: FolderTreeItemResponseModel = { - $type: 'FolderTreeItemResponseModel', - parentId: folderRequest.parentId, - name: folderRequest.name, - id: folderRequest.id, - isFolder: true, - isContainer: false, - type: 'data-type', - icon: 'umb:folder', - hasChildren: false, - }; - - this.#treeStore?.appendItems([treeItem]); + // TODO: We need to push a new item to the tree store to update the tree. How do we want to create the tree items? + const folderTreeItem = createFolderTreeItem(folderRequest); + this.#treeStore?.appendItems([folderTreeItem]); } return { error }; @@ -279,3 +269,29 @@ export class UmbDataTypeRepository return { data, error }; } } + +export const createTreeItem = (item: CreateDataTypeRequestModel): FolderTreeItemResponseModel => { + if (!item) throw new Error('item is null or undefined'); + if (!item.id) throw new Error('item.id is null or undefined'); + + return { + $type: 'FolderTreeItemResponseModel', + type: 'data-type', + parentId: item.parentId, + name: item.name, + id: item.id, + isFolder: false, + isContainer: false, + hasChildren: false, + }; +}; + +export const createFolderTreeItem = (item: CreateFolderRequestModel): FolderTreeItemResponseModel => { + if (!item) throw new Error('item is null or undefined'); + if (!item.id) throw new Error('item.id is null or undefined'); + + return { + ...createTreeItem(item), + isFolder: true, + }; +}; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/sources/data-type.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/sources/data-type.server.data.ts index 9ef4e96de1..0d70c24d85 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/sources/data-type.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/sources/data-type.server.data.ts @@ -17,8 +17,7 @@ import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; * @implements {RepositoryDetailDataSource} */ export class UmbDataTypeServerDataSource - implements - UmbDataSource + implements UmbDataSource { #host: UmbControllerHostElement; @@ -50,14 +49,13 @@ export class UmbDataTypeServerDataSource /** * Creates a new Data Type scaffold * @param {(string | null)} parentId - * @return {*} + * @return { CreateDataTypeRequestModel } * @memberof UmbDataTypeServerDataSource */ - async createScaffold(parentId: string | null) { - const data: DataTypeResponseModel = { - $type: '', - parentId: parentId, + async createScaffold(parentId?: string | null) { + const data: CreateDataTypeRequestModel = { id: uuidv4(), + parentId, }; return { data }; @@ -73,7 +71,7 @@ export class UmbDataTypeServerDataSource if (!dataType) throw new Error('Data Type is missing'); if (!dataType.id) throw new Error('Data Type id is missing'); - tryExecuteAndNotify( + return tryExecuteAndNotify( this.#host, DataTypeResource.postDataType({ requestBody: dataType, diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts index a1e6fd139a..127850dc1f 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts @@ -1,7 +1,7 @@ import { UmbWorkspaceContext } from '../../../shared/components/workspace/workspace-context/workspace-context'; import { UmbDataTypeRepository } from '../repository/data-type.repository'; import { UmbEntityWorkspaceContextInterface } from '@umbraco-cms/backoffice/workspace'; -import type { DataTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import type { CreateDataTypeRequestModel, DataTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { appendToFrozenArray, ObjectState } from '@umbraco-cms/backoffice/observable-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; @@ -9,8 +9,10 @@ export class UmbDataTypeWorkspaceContext extends UmbWorkspaceContext implements UmbEntityWorkspaceContextInterface { + // TODO: revisit. temp solution because the create and response models are different. #data = new ObjectState(undefined); data = this.#data.asObservable(); + name = this.#data.getObservablePart((data) => data?.name); id = this.#data.getObservablePart((data) => data?.id); @@ -29,7 +31,9 @@ export class UmbDataTypeWorkspaceContext async createScaffold(parentId: string | null) { const { data } = await this.repository.createScaffold(parentId); this.setIsNew(true); - this.#data.next(data); + // TODO: This is a hack to get around the fact that the data is not typed correctly. + // Create and response models are different. We need to look into this. + this.#data.next(data as unknown as DataTypeResponseModel); return { data }; } @@ -70,10 +74,12 @@ export class UmbDataTypeWorkspaceContext async save() { if (!this.#data.value) return; + if (!this.#data.value.id) return; + if (this.isNew) { await this.repository.create(this.#data.value); } else { - await this.repository.save(this.#data.value); + await this.repository.save(this.#data.value.id, this.#data.value); } // If it went well, then its not new anymore?. this.setIsNew(false); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.element.ts index 0ea7ef470f..69f1f01de7 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.element.ts @@ -21,7 +21,7 @@ export class UmbDataTypeWorkspaceElement extends UmbLitElement { path: 'create/:parentId', component: () => this.#element, setup: (_component, info) => { - const parentId = info.match.params.parentId; + const parentId = info.match.params.parentId === 'null' ? null : info.match.params.parentId; this.#workspaceContext.createScaffold(parentId); }, }, diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/info/workspace-view-data-type-info.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/info/workspace-view-data-type-info.element.ts index 0bd1802687..03b7ef49a0 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/info/workspace-view-data-type-info.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/info/workspace-view-data-type-info.element.ts @@ -9,7 +9,15 @@ import { DataTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; @customElement('umb-workspace-view-data-type-info') export class UmbWorkspaceViewDataTypeInfoElement extends UmbLitElement { - static styles = [UUITextStyles, css``]; + static styles = [ + UUITextStyles, + css` + :host { + display: block; + margin: var(--uui-size-layout-1); + } + `, + ]; @state() _dataType?: DataTypeResponseModel; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/relation-types/repository/relation-type.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/relation-types/repository/relation-type.repository.ts index 86e30adfbf..52249a2896 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/relation-types/repository/relation-type.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/relation-types/repository/relation-type.repository.ts @@ -5,18 +5,20 @@ import { RelationTypeTreeServerDataSource } from './sources/relation-type.tree.s import { RelationTypeTreeDataSource } from './sources'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; -import { ProblemDetailsModel, RelationTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { + CreateRelationTypeRequestModel, + ProblemDetailsModel, + RelationTypeResponseModel, + UpdateRelationTypeRequestModel, +} from '@umbraco-cms/backoffice/backend-api'; import { UmbDetailRepository, UmbTreeRepository } from '@umbraco-cms/backoffice/repository'; import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification'; -type ItemType = RelationTypeResponseModel; -type TreeItemType = any; - -// Move to documentation / JSdoc -/* We need to create a new instance of the repository from within the element context. We want the notifications to be displayed in the right context. */ -// element -> context -> repository -> (store) -> data source -// All methods should be async and return a promise. Some methods might return an observable as part of the promise response. -export class UmbRelationTypeRepository implements UmbTreeRepository, UmbDetailRepository { +export class UmbRelationTypeRepository + implements + UmbTreeRepository, + UmbDetailRepository +{ #init!: Promise; #host: UmbControllerHostElement; @@ -73,13 +75,9 @@ export class UmbRelationTypeRepository implements UmbTreeRepository) { + if (!ids) throw new Error('Ids are missing'); await this.#init; - if (!ids) { - const error: ProblemDetailsModel = { title: 'Keys are missing' }; - return { data: undefined, error }; - } - const { data, error } = await this.#treeSource.getItems(ids); return { data, error, asObservable: () => this.#treeStore!.items(ids) }; @@ -137,75 +135,72 @@ export class UmbRelationTypeRepository implements UmbTreeRepository>; - get(id: string): Promise>; - insert(template: TemplateResponseModel): Promise; - update(template: TemplateResponseModel): Promise; - delete(id: string): Promise; -} +import type { UmbDataSource } from '@umbraco-cms/backoffice/repository'; /** * A data source for the Template detail that fetches data from the server @@ -18,7 +15,9 @@ export interface TemplateDetailDataSource { * @class UmbTemplateDetailServerDataSource * @implements {TemplateDetailDataSource} */ -export class UmbTemplateDetailServerDataSource implements TemplateDetailDataSource { +export class UmbTemplateDetailServerDataSource + implements UmbDataSource +{ #host: UmbControllerHostElement; /** @@ -77,13 +76,13 @@ export class UmbTemplateDetailServerDataSource implements TemplateDetailDataSour * @return {*} * @memberof UmbTemplateDetailServerDataSource */ - async insert(template: TemplateResponseModel) { - const payload = { requestBody: template }; - // TODO: fix type mismatch + async insert(template: CreateTemplateRequestModel) { + if (!template) throw new Error('Template is missing'); + return tryExecuteAndNotify( this.#host, - tryExecuteAndNotify(this.#host, TemplateResource.postTemplate(payload)) as any - ) as any; + tryExecuteAndNotify(this.#host, TemplateResource.postTemplate({ requestBody: template })) + ); } /** @@ -92,14 +91,10 @@ export class UmbTemplateDetailServerDataSource implements TemplateDetailDataSour * @return {*} * @memberof UmbTemplateDetailServerDataSource */ - async update(template: TemplateResponseModel) { - if (!template.id) { - const error: ProblemDetailsModel = { title: 'Template id is missing' }; - return { error }; - } - - const payload = { id: template.id, requestBody: template }; - return tryExecuteAndNotify(this.#host, TemplateResource.putTemplateById(payload)); + async update(id: string, template: UpdateTemplateRequestModel) { + if (!id) throw new Error('Id is missing'); + if (!template) throw new Error('Template is missing'); + return tryExecuteAndNotify(this.#host, TemplateResource.putTemplateById({ id, requestBody: template })); } /** @@ -109,11 +104,7 @@ export class UmbTemplateDetailServerDataSource implements TemplateDetailDataSour * @memberof UmbTemplateDetailServerDataSource */ async delete(id: string) { - if (!id) { - const error: ProblemDetailsModel = { title: 'Key is missing' }; - return { error }; - } - + if (!id) throw new Error('Id is missing'); return await tryExecuteAndNotify(this.#host, TemplateResource.deleteTemplateById({ id })); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/repository/template.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/repository/template.repository.ts index a41048ff2a..0ec0c4baa7 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/repository/template.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/repository/template.repository.ts @@ -6,9 +6,18 @@ import type { UmbDetailRepository, UmbTreeRepository } from '@umbraco-cms/backof import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification'; import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; -import { ProblemDetailsModel, TemplateResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { + CreateTemplateRequestModel, + ProblemDetailsModel, + TemplateResponseModel, + UpdateTemplateRequestModel, +} from '@umbraco-cms/backoffice/backend-api'; -export class UmbTemplateRepository implements UmbTreeRepository, UmbDetailRepository { +export class UmbTemplateRepository + implements + UmbTreeRepository, + UmbDetailRepository +{ #init; #host: UmbControllerHostElement; @@ -156,26 +165,25 @@ export class UmbTemplateRepository implements UmbTreeRepository, UmbDetailR return { error }; } - async save(template: TemplateResponseModel) { + async save(id: string, template: UpdateTemplateRequestModel) { + if (!id) throw new Error('Id is missing'); + if (!template) throw new Error('Template is missing'); + await this.#init; - if (!template || !template.id) { - throw new Error('Template is missing'); - } - - const { error } = await this.#detailDataSource.update(template); + const { error } = await this.#detailDataSource.update(id, template); if (!error) { const notification = { data: { message: `Template saved` } }; this.#notificationContext?.peek('positive', notification); - } - // TODO: we currently don't use the detail store for anything. - // Consider to look up the data before fetching from the server - // Consider notify a workspace if a template is updated in the store while someone is editing it. - this.#store?.append(template); - this.#treeStore?.updateItem(template.id, { name: template.name }); - // TODO: would be nice to align the stores on methods/methodNames. + // TODO: we currently don't use the detail store for anything. + // Consider to look up the data before fetching from the server + // Consider notify a workspace if a template is updated in the store while someone is editing it. + // TODO: would be nice to align the stores on methods/methodNames. + //this.#store?.append(template); + this.#treeStore?.updateItem(id, template); + } return { error }; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dashboards/dictionary/dashboard-translation-dictionary.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dashboards/dictionary/dashboard-translation-dictionary.element.ts index 6f187c444e..031192d45b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dashboards/dictionary/dashboard-translation-dictionary.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dashboards/dictionary/dashboard-translation-dictionary.element.ts @@ -155,6 +155,7 @@ export class UmbDashboardTranslationDictionaryElement extends UmbLitElement { async #create() { // TODO: what to do if modal service is not available? if (!this.#modalContext) return; + if (!this.#repo) return; const modalHandler = this.#modalContext?.open(UMB_CREATE_DICTIONARY_MODAL, { unique: null }); @@ -162,8 +163,8 @@ export class UmbDashboardTranslationDictionaryElement extends UmbLitElement { const { name } = await modalHandler.onSubmit(); if (!name) return; - const result = await this.#repo?.create({ $type: '', name, parentId: null, translations: [], id: '' }); - + const { data } = await this.#repo.createScaffold(null); + console.log(data); // TODO => get location header to route to new item } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/create/create.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/create/create.action.ts index dae8885787..34bb2459bf 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/create/create.action.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/create/create.action.ts @@ -34,6 +34,7 @@ export default class UmbCreateDictionaryEntityAction extends UmbEntityActionBase async execute() { // TODO: what to do if modal service is not available? if (!this.#modalContext) return; + if (!this.repository) return; // TODO: how can we get the current entity detail in the modal? Passing the observable // feels a bit hacky. Works, but hacky. @@ -46,15 +47,9 @@ export default class UmbCreateDictionaryEntityAction extends UmbEntityActionBase const { name } = await modalHandler.onSubmit(); if (!name) return; - const result = await this.repository?.create({ - $type: '', - name, - parentId: this.unique, - translations: [], - id: '', - }); + const { data } = await this.repository.createScaffold(this.unique, name); // TODO => get location header to route to new item - console.log(result); + console.log(data); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/index.ts deleted file mode 100644 index b478fc4193..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { DictionaryItemTranslationModel, EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; - -// TODO: Can we get rid of this type? I guess it should come from the server? Investigate this. -export interface DictionaryDetails extends EntityTreeItemResponseModel { - translations: DictionaryItemTranslationModel[]; -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts index aeba6365eb..0ce6e7db0f 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts @@ -1,4 +1,3 @@ -import type { DictionaryDetails } from '../'; import { UmbDictionaryStore, UMB_DICTIONARY_STORE_CONTEXT_TOKEN } from './dictionary.store'; import { UmbDictionaryDetailServerDataSource } from './sources/dictionary.detail.server.data'; import { UmbDictionaryTreeStore, UMB_DICTIONARY_TREE_STORE_CONTEXT_TOKEN } from './dictionary.tree.store'; @@ -6,10 +5,24 @@ import { DictionaryTreeServerDataSource } from './sources/dictionary.tree.server import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; import { UmbTreeDataSource, UmbDetailRepository, UmbTreeRepository } from '@umbraco-cms/backoffice/repository'; -import { ImportDictionaryRequestModel, ProblemDetailsModel } from '@umbraco-cms/backoffice/backend-api'; +import { + CreateDictionaryItemRequestModel, + DictionaryOverviewResponseModel, + ImportDictionaryRequestModel, + ProblemDetailsModel, + UpdateDictionaryItemRequestModel, +} from '@umbraco-cms/backoffice/backend-api'; import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification'; -export class UmbDictionaryRepository implements UmbTreeRepository, UmbDetailRepository { +export class UmbDictionaryRepository + implements + UmbTreeRepository, + UmbDetailRepository< + CreateDictionaryItemRequestModel, + UpdateDictionaryItemRequestModel, + DictionaryOverviewResponseModel + > +{ #init!: Promise; #host: UmbControllerHostElement; @@ -103,26 +116,16 @@ export class UmbDictionaryRepository implements UmbTreeRepository, UmbDetailRepo // DETAILS - async createScaffold(parentId: string | null) { + async createScaffold(parentId: string | null, name?: string) { + if (parentId === undefined) throw new Error('Parent id is missing'); await this.#init; - - if (!parentId) { - const error: ProblemDetailsModel = { title: 'Parent id is missing' }; - return { data: undefined, error }; - } - - return this.#detailSource.createScaffold(parentId); + return this.#detailSource.createScaffold(parentId, name); } async requestById(id: string) { + if (!id) throw new Error('Id is missing'); await this.#init; - // TODO: should we show a notification if the id is missing? - // Investigate what is best for Acceptance testing, cause in that perspective a thrown error might be the best choice? - if (!id) { - const error: ProblemDetailsModel = { title: 'Id is missing' }; - return { error }; - } const { data, error } = await this.#detailSource.get(id); if (data) { @@ -141,34 +144,30 @@ export class UmbDictionaryRepository implements UmbTreeRepository, UmbDetailRepo return this.#detailSource.delete(id); } - async save(dictionary: DictionaryDetails) { + async save(id: string, updatedDictionary: UpdateDictionaryItemRequestModel) { + if (!id) throw new Error('Id is missing'); + if (!updatedDictionary) throw new Error('Dictionary is missing'); + await this.#init; - // TODO: should we show a notification if the dictionary is missing? - // Investigate what is best for Acceptance testing, cause in that perspective a thrown error might be the best choice? - if (!dictionary || !dictionary.id) { - const error: ProblemDetailsModel = { title: 'Dictionary is missing' }; - return { error }; - } - - const { error } = await this.#detailSource.update(dictionary); + const { error } = await this.#detailSource.update(id, updatedDictionary); if (!error) { - const notification = { data: { message: `Dictionary '${dictionary.name}' saved` } }; + // TODO: we currently don't use the detail store for anything. + // Consider to look up the data before fetching from the server + // Consider notify a workspace if a dictionary is updated in the store while someone is editing it. + // TODO: would be nice to align the stores on methods/methodNames. + //this.#detailStore?.append(dictionary); + this.#treeStore?.updateItem(id, { name: updatedDictionary.name }); + + const notification = { data: { message: `Dictionary '${updatedDictionary.name}' saved` } }; this.#notificationContext?.peek('positive', notification); } - // TODO: we currently don't use the detail store for anything. - // Consider to look up the data before fetching from the server - // Consider notify a workspace if a dictionary is updated in the store while someone is editing it. - this.#detailStore?.append(dictionary); - this.#treeStore?.updateItem(dictionary.id, { name: dictionary.name }); - // TODO: would be nice to align the stores on methods/methodNames. - return { error }; } - async create(detail: DictionaryDetails) { + async create(detail: CreateDictionaryItemRequestModel) { await this.#init; if (!detail.name) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.store.ts index a9907ca76a..c4b62e7c82 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.store.ts @@ -1,8 +1,8 @@ -import type { DictionaryDetails } from '../'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; import { UmbStoreBase } from '@umbraco-cms/backoffice/store'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; import { ArrayState } from '@umbraco-cms/backoffice/observable-api'; +import { DictionaryItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; /** * @export @@ -11,13 +11,13 @@ import { ArrayState } from '@umbraco-cms/backoffice/observable-api'; * @description - Data Store for Dictionary */ export class UmbDictionaryStore extends UmbStoreBase { - #data = new ArrayState([], (x) => x.id); + #data = new ArrayState([], (x) => x.id); constructor(host: UmbControllerHostElement) { super(host, UMB_DICTIONARY_STORE_CONTEXT_TOKEN.toString()); } - append(dictionary: DictionaryDetails) { + append(dictionary: DictionaryItemResponseModel) { this.#data.append([dictionary]); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.detail.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.detail.server.data.ts index 4de279d9a0..3479db2d77 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.detail.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.detail.server.data.ts @@ -1,14 +1,16 @@ -import type { DictionaryDetails } from '../../'; -import { DictionaryDetailDataSource } from './dictionary.details.server.data.interface'; +import { v4 as uuidv4 } from 'uuid'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; import { CreateDictionaryItemRequestModel, + DictionaryItemResponseModel, DictionaryResource, ImportDictionaryRequestModel, LanguageResource, ProblemDetailsModel, + UpdateDictionaryItemRequestModel, } from '@umbraco-cms/backoffice/backend-api'; +import { UmbDataSource } from '@umbraco-cms/backoffice/repository'; /** * @description - A data source for the Dictionary detail that fetches data from the server @@ -16,7 +18,10 @@ import { * @class UmbDictionaryDetailServerDataSource * @implements {DictionaryDetailDataSource} */ -export class UmbDictionaryDetailServerDataSource implements DictionaryDetailDataSource { +export class UmbDictionaryDetailServerDataSource + implements + UmbDataSource +{ #host: UmbControllerHostElement; constructor(host: UmbControllerHostElement) { @@ -29,11 +34,13 @@ export class UmbDictionaryDetailServerDataSource implements DictionaryDetailData * @return {*} * @memberof UmbDictionaryDetailServerDataSource */ - async createScaffold(parentId: string) { - const data: DictionaryDetails = { - name: '', + async createScaffold(parentId?: string | null, name?: string) { + const data = { + id: uuidv4(), parentId, - } as DictionaryDetails; + name, + translations: [], + }; return { data }; } @@ -45,7 +52,7 @@ export class UmbDictionaryDetailServerDataSource implements DictionaryDetailData * @memberof UmbDictionaryDetailServerDataSource */ get(id: string) { - return tryExecuteAndNotify(this.#host, DictionaryResource.getDictionaryById({ id })) as any; + return tryExecuteAndNotify(this.#host, DictionaryResource.getDictionaryById({ id })); } /** @@ -64,13 +71,11 @@ export class UmbDictionaryDetailServerDataSource implements DictionaryDetailData * @return {*} * @memberof UmbDictionaryDetailServerDataSource */ - async update(dictionary: DictionaryDetails) { - if (!dictionary.id) { - const error: ProblemDetailsModel = { title: 'Dictionary id is missing' }; - return { error }; - } + async update(id: string, dictionary: UpdateDictionaryItemRequestModel) { + if (!id) throw new Error('Id is missing'); + if (!dictionary) throw new Error('Dictionary is missing'); - const payload = { id: dictionary.id, requestBody: dictionary }; + const payload = { id, requestBody: dictionary }; return tryExecuteAndNotify(this.#host, DictionaryResource.putDictionaryById(payload)); } @@ -80,14 +85,8 @@ export class UmbDictionaryDetailServerDataSource implements DictionaryDetailData * @return {*} * @memberof UmbDictionaryDetailServerDataSource */ - async insert(data: DictionaryDetails) { - const requestBody: CreateDictionaryItemRequestModel = { - parentId: data.parentId, - name: data.name, - }; - - // TODO: fix type mismatch: - return tryExecuteAndNotify(this.#host, DictionaryResource.postDictionary({ requestBody })) as any; + async insert(data: CreateDictionaryItemRequestModel) { + return tryExecuteAndNotify(this.#host, DictionaryResource.postDictionary({ requestBody: data })); } /** diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.details.server.data.interface.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.details.server.data.interface.ts deleted file mode 100644 index 269a4610b0..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.details.server.data.interface.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { DictionaryDetails } from '../../'; -import { - DictionaryItemResponseModel, - PagedDictionaryOverviewResponseModel, - PagedLanguageResponseModel, - ImportDictionaryRequestModel, -} from '@umbraco-cms/backoffice/backend-api'; -import type { DataSourceResponse } from '@umbraco-cms/backoffice/repository'; - -export interface DictionaryDetailDataSource { - createScaffold(parentId: string): Promise>; - list(skip?: number, take?: number): Promise>; - get(id: string): Promise>; - insert(data: DictionaryDetails): Promise; - update(dictionary: DictionaryItemResponseModel): Promise; - delete(id: string): Promise; - export(id: string, includeChildren: boolean): Promise>; - import(fileName: string, parentId?: string): Promise>; - upload(formData: ImportDictionaryRequestModel): Promise>; - // TODO - temp only - getLanguages(): Promise>; -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts index dc0cdb3580..2d2eb3039d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts @@ -1,18 +1,17 @@ import { UmbDictionaryRepository } from '../repository/dictionary.repository'; import { UmbWorkspaceContext } from '../../../../backoffice/shared/components/workspace/workspace-context/workspace-context'; -import type { DictionaryDetails } from '../'; import { UmbEntityWorkspaceContextInterface } from '@umbraco-cms/backoffice/workspace'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; import { ObjectState } from '@umbraco-cms/backoffice/observable-api'; - -type EntityType = DictionaryDetails; +import { DictionaryItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; export class UmbDictionaryWorkspaceContext - extends UmbWorkspaceContext - implements UmbEntityWorkspaceContextInterface + extends UmbWorkspaceContext + implements UmbEntityWorkspaceContextInterface { - #data = new ObjectState(undefined); + #data = new ObjectState(undefined); data = this.#data.asObservable(); + name = this.#data.getObservablePart((data) => data?.name); dictionary = this.#data.getObservablePart((data) => data); @@ -68,12 +67,15 @@ export class UmbDictionaryWorkspaceContext const { data } = await this.repository.createScaffold(parentId); if (!data) return; this.setIsNew(true); - this.#data.next(data); + // TODO: This is a hack to get around the fact that the data is not typed correctly. + // Create and response models are different. We need to look into this. + this.#data.next(data as unknown as DictionaryItemResponseModel); } async save() { if (!this.#data.value) return; - await this.repository.save(this.#data.value); + if (!this.#data.value.id) return; + await this.repository.save(this.#data.value.id, this.#data.value); this.setIsNew(false); } diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/browser-handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/browser-handlers.ts index a756d4c727..e899c2aaf6 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/browser-handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/browser-handlers.ts @@ -1,4 +1,4 @@ -import { handlers as dataTypeHandlers } from './domains/data-type.handlers'; +import { handlers as dataTypeHandlers } from './domains/data-type'; import { handlers as relationTypeHandlers } from './domains/relation-type.handlers'; import { handlers as documentTypeHandlers } from './domains/document-type.handlers'; import { handlers as installHandlers } from './domains/install.handlers'; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/data/dictionary.data.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/data/dictionary.data.ts index c25318b39f..aaf776a892 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/data/dictionary.data.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/data/dictionary.data.ts @@ -1,18 +1,12 @@ -import type { DictionaryDetails } from '../../../backoffice/translation/dictionary'; import { UmbEntityData } from './entity.data'; import { createEntityTreeItem } from './utils'; -import type { EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import type { DictionaryItemResponseModel, EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; -export const data: Array = [ +export const data: Array = [ { $type: '', - parentId: null, name: 'Hello', id: 'aae7d0ab-53ba-485d-b8bd-12537f9925cb', - hasChildren: true, - type: 'dictionary-item', - isContainer: false, - icon: 'umb:book-alt', translations: [ { isoCode: 'en', @@ -26,13 +20,8 @@ export const data: Array = [ }, { $type: '', - parentId: 'aae7d0ab-53ba-485d-b8bd-12537f9925cb', name: 'Hello again', id: 'bbe7d0ab-53bb-485d-b8bd-12537f9925cb', - hasChildren: false, - type: 'dictionary-item', - isContainer: false, - icon: 'umb:book-alt', translations: [ { isoCode: 'en', @@ -46,27 +35,50 @@ export const data: Array = [ }, ]; +const dictionaryTree: Array = [ + { + $type: '', + parentId: null, + name: 'Hello', + id: 'aae7d0ab-53ba-485d-b8bd-12537f9925cb', + hasChildren: true, + type: 'dictionary-item', + isContainer: false, + icon: 'umb:book-alt', + }, + { + $type: '', + parentId: 'aae7d0ab-53ba-485d-b8bd-12537f9925cb', + name: 'Hello again', + id: 'bbe7d0ab-53bb-485d-b8bd-12537f9925cb', + hasChildren: false, + type: 'dictionary-item', + isContainer: false, + icon: 'umb:book-alt', + }, +]; + // Temp mocked database // TODO: all properties are optional in the server schema. I don't think this is correct. // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore -class UmbDictionaryData extends UmbEntityData { +class UmbDictionaryData extends UmbEntityData { constructor() { super(data); } getTreeRoot(): Array { - const rootItems = this.data.filter((item) => item.parentId === null); + const rootItems = dictionaryTree.filter((item) => item.parentId === null); return rootItems.map((item) => createEntityTreeItem(item)); } getTreeItemChildren(id: string): Array { - const childItems = this.data.filter((item) => item.parentId === id); + const childItems = dictionaryTree.filter((item) => item.parentId === id); return childItems.map((item) => createEntityTreeItem(item)); } getTreeItem(ids: Array): Array { - const items = this.data.filter((item) => ids.includes(item.id ?? '')); + const items = dictionaryTree.filter((item) => ids.includes(item.id ?? '')); return items.map((item) => createEntityTreeItem(item)); } } diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/data/entity.data.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/data/entity.data.ts index 9237f22a99..d3a7356430 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/data/entity.data.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/data/entity.data.ts @@ -19,6 +19,16 @@ export class UmbEntityData extends UmbData { return this.data.filter((item) => ids.includes(item.id)); } + insert(item: T) { + const exits = this.data.find((i) => i.id === item.id); + + if (exits) { + throw new Error(`Item with key ${item.id} already exists`); + } + + this.data.push(item); + } + save(saveItem: T) { const foundIndex = this.data.findIndex((item) => item.id === saveItem.id); if (foundIndex !== -1) { diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type.handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type.handlers.ts deleted file mode 100644 index 0c3c6f75e5..0000000000 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type.handlers.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { rest } from 'msw'; -import { umbDataTypeData } from '../data/data-type.data'; -import { umbracoPath } from '@umbraco-cms/backoffice/utils'; -import { ProblemDetailsModel } from '@umbraco-cms/backoffice/backend-api'; - -// TODO: add schema -export const handlers = [ - // TREE - rest.get(umbracoPath('/tree/data-type/root'), (req, res, ctx) => { - const rootItems = umbDataTypeData.getTreeRoot(); - const response = { - total: rootItems.length, - items: rootItems, - }; - return res(ctx.status(200), ctx.json(response)); - }), - - rest.get(umbracoPath('/tree/data-type/children'), (req, res, ctx) => { - const parentId = req.url.searchParams.get('parentId'); - if (!parentId) return; - - const children = umbDataTypeData.getTreeItemChildren(parentId); - - const response = { - total: children.length, - items: children, - }; - - return res(ctx.status(200), ctx.json(response)); - }), - - rest.get(umbracoPath('/tree/data-type/item'), (req, res, ctx) => { - const ids = req.url.searchParams.getAll('id'); - if (!ids) return; - const items = umbDataTypeData.getTreeItem(ids); - return res(ctx.status(200), ctx.json(items)); - }), - - // FOLDERS - rest.post(umbracoPath('/data-type/folder'), async (req, res, ctx) => { - const data = await req.json(); - if (!data) return; - - umbDataTypeData.createFolder(data); - - return res(ctx.status(200)); - }), - - rest.get(umbracoPath('/data-type/folder/:id'), (req, res, ctx) => { - const id = req.params.id as string; - if (!id) return; - - const dataType = umbDataTypeData.getById(id); - - return res(ctx.status(200), ctx.json(dataType)); - }), - - rest.put(umbracoPath('/data-type/folder/:id'), async (req, res, ctx) => { - const data = await req.json(); - if (!data) return; - - umbDataTypeData.save(data); - - return res(ctx.status(200)); - }), - - rest.delete(umbracoPath('/data-type/folder/:id'), async (req, res, ctx) => { - const id = req.params.id as string; - if (!id) return; - - try { - umbDataTypeData.deleteFolder(id); - return res(ctx.status(200)); - } catch (error) { - return res( - ctx.status(404), - ctx.json({ - status: 404, - type: 'error', - detail: 'Not Found', - }) - ); - } - }), - - // Details - rest.post(umbracoPath('/data-type'), async (req, res, ctx) => { - const data = await req.json(); - if (!data) return; - - const saved = umbDataTypeData.save(data); - - return res(ctx.status(200), ctx.json(saved)); - }), - - rest.get(umbracoPath('/data-type/:id'), (req, res, ctx) => { - const id = req.params.id as string; - if (!id) return; - - const dataType = umbDataTypeData.getById(id); - - return res(ctx.status(200), ctx.json(dataType)); - }), - - rest.put(umbracoPath('/data-type/:id'), async (req, res, ctx) => { - const data = await req.json(); - if (!data) return; - - const saved = umbDataTypeData.save(data); - - return res(ctx.status(200), ctx.json(saved)); - }), - - rest.delete(umbracoPath('/data-type/:id'), async (req, res, ctx) => { - const id = req.params.id as string; - if (!id) return; - - umbDataTypeData.delete([id]); - - return res(ctx.status(200)); - }), -]; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/detail.handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/detail.handlers.ts new file mode 100644 index 0000000000..c362a16141 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/detail.handlers.ts @@ -0,0 +1,42 @@ +import { rest } from 'msw'; +import { umbDataTypeData } from '../../data/data-type.data'; +import { slug } from './slug'; +import { umbracoPath } from '@umbraco-cms/backoffice/utils'; + +export const detailHandlers = [ + rest.post(umbracoPath(`${slug}`), async (req, res, ctx) => { + const data = await req.json(); + if (!data) return; + + umbDataTypeData.insert(data); + + return res(ctx.status(200)); + }), + + rest.get(umbracoPath(`${slug}/:id`), (req, res, ctx) => { + const id = req.params.id as string; + if (!id) return; + + const dataType = umbDataTypeData.getById(id); + + return res(ctx.status(200), ctx.json(dataType)); + }), + + rest.put(umbracoPath(`${slug}/:id`), async (req, res, ctx) => { + const data = await req.json(); + if (!data) return; + + const saved = umbDataTypeData.save(data); + + return res(ctx.status(200), ctx.json(saved)); + }), + + rest.delete(umbracoPath(`${slug}/:id`), async (req, res, ctx) => { + const id = req.params.id as string; + if (!id) return; + + umbDataTypeData.delete([id]); + + return res(ctx.status(200)); + }), +]; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/folder.handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/folder.handlers.ts new file mode 100644 index 0000000000..9a0d5bc071 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/folder.handlers.ts @@ -0,0 +1,53 @@ +import { rest } from 'msw'; +import { umbDataTypeData } from '../../data/data-type.data'; +import { slug } from './slug'; +import { umbracoPath } from '@umbraco-cms/backoffice/utils'; +import { ProblemDetailsModel } from '@umbraco-cms/backoffice/backend-api'; + +export const folderHandlers = [ + rest.post(umbracoPath(`${slug}/folder`), async (req, res, ctx) => { + const data = await req.json(); + if (!data) return; + + umbDataTypeData.createFolder(data); + + return res(ctx.status(200)); + }), + + rest.get(umbracoPath(`${slug}/folder/:id`), (req, res, ctx) => { + const id = req.params.id as string; + if (!id) return; + + const dataType = umbDataTypeData.getById(id); + + return res(ctx.status(200), ctx.json(dataType)); + }), + + rest.put(umbracoPath(`${slug}/folder/:id`), async (req, res, ctx) => { + const data = await req.json(); + if (!data) return; + + umbDataTypeData.save(data); + + return res(ctx.status(200)); + }), + + rest.delete(umbracoPath(`${slug}/folder/:id`), async (req, res, ctx) => { + const id = req.params.id as string; + if (!id) return; + + try { + umbDataTypeData.deleteFolder(id); + return res(ctx.status(200)); + } catch (error) { + return res( + ctx.status(404), + ctx.json({ + status: 404, + type: 'error', + detail: 'Not Found', + }) + ); + } + }), +]; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/index.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/index.ts new file mode 100644 index 0000000000..fb46e23ffd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/index.ts @@ -0,0 +1,6 @@ +import { folderHandlers } from './folder.handlers'; +import { treeHandlers } from './tree.handlers'; +import { detailHandlers } from './detail.handlers'; +import { itemHandlers } from './item.handlers'; + +export const handlers = [...treeHandlers, ...itemHandlers, ...folderHandlers, ...detailHandlers]; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/item.handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/item.handlers.ts new file mode 100644 index 0000000000..24d17c79e0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/item.handlers.ts @@ -0,0 +1,13 @@ +import { rest } from 'msw'; +import { umbDataTypeData } from '../../data/data-type.data'; +import { slug } from './slug'; +import { umbracoPath } from '@umbraco-cms/backoffice/utils'; + +export const itemHandlers = [ + rest.get(umbracoPath(`${slug}/item`), (req, res, ctx) => { + const ids = req.url.searchParams.getAll('id'); + if (!ids) return; + const items = umbDataTypeData.getTreeItem(ids); + return res(ctx.status(200), ctx.json(items)); + }), +]; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/slug.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/slug.ts new file mode 100644 index 0000000000..5c453df84c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/slug.ts @@ -0,0 +1 @@ +export const slug = '/data-type'; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/tree.handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/tree.handlers.ts new file mode 100644 index 0000000000..203acd560f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/data-type/tree.handlers.ts @@ -0,0 +1,29 @@ +import { rest } from 'msw'; +import { umbDataTypeData } from '../../data/data-type.data'; +import { slug } from './slug'; +import { umbracoPath } from '@umbraco-cms/backoffice/utils'; + +export const treeHandlers = [ + rest.get(umbracoPath(`/tree${slug}/root`), (req, res, ctx) => { + const rootItems = umbDataTypeData.getTreeRoot(); + const response = { + total: rootItems.length, + items: rootItems, + }; + return res(ctx.status(200), ctx.json(response)); + }), + + rest.get(umbracoPath(`/tree${slug}/children`), (req, res, ctx) => { + const parentId = req.url.searchParams.get('parentId'); + if (!parentId) return; + + const children = umbDataTypeData.getTreeItemChildren(parentId); + + const response = { + total: children.length, + items: children, + }; + + return res(ctx.status(200), ctx.json(response)); + }), +]; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/dictionary.handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/dictionary.handlers.ts index 16fbe9f341..33b51c2ad4 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/dictionary.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/dictionary.handlers.ts @@ -1,15 +1,19 @@ import { rest } from 'msw'; -import type { DictionaryDetails } from '../../../backoffice/translation/dictionary'; import { umbDictionaryData } from '../data/dictionary.data'; -import { ImportDictionaryRequestModel, DictionaryOverviewResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { + ImportDictionaryRequestModel, + DictionaryOverviewResponseModel, + DictionaryItemResponseModel, + EntityTreeItemResponseModel, +} from '@umbraco-cms/backoffice/backend-api'; const uploadResponse: ImportDictionaryRequestModel = { temporaryFileId: 'c:/path/to/tempfilename.udt', parentId: 'b7e7d0ab-53ba-485d-dddd-12537f9925aa', }; -/// -const importResponse: DictionaryDetails = { +/// TODO: get correct type +const importResponse: DictionaryItemResponseModel & EntityTreeItemResponseModel = { $type: '', parentId: null, name: 'Uploaded dictionary', diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/e2e-handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/e2e-handlers.ts index eb8be4c283..e0a5d8efe6 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/e2e-handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/e2e-handlers.ts @@ -1,4 +1,4 @@ -import { handlers as dataTypeHandlers } from './domains/data-type.handlers'; +import { handlers as dataTypeHandlers } from './domains/data-type'; import { handlers as documentTypeHandlers } from './domains/document-type.handlers'; import { handlers as installHandlers } from './domains/install.handlers'; import * as manifestsHandlers from './domains/manifests.handlers';