diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts index 9817527849..dae0f7ba21 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts @@ -23,8 +23,8 @@ import { UmbDocumentStore } from './documents/documents/repository/document.stor import { UmbDocumentTreeStore } from './documents/documents/repository/document.tree.store'; import { UmbMediaDetailStore } from './media/media/repository/media.detail.store'; import { UmbMediaTreeStore } from './media/media/repository/media.tree.store'; -import { UmbMemberTypeDetailStore } from './members/member-types/member-type.detail.store'; -import { UmbMemberTypeTreeStore } from './members/member-types/member-type.tree.store'; +import { UmbMemberTypeDetailStore } from './members/member-types/repository/member-type.detail.store'; +import { UmbMemberTypeTreeStore } from './members/member-types/repository/member-type.tree.store'; import { UmbMemberGroupDetailStore } from './members/member-groups/repository/member-group.detail.store'; import { UmbMemberGroupTreeStore } from './members/member-groups/repository/member-group.tree.store'; import { UmbMemberDetailStore } from './members/members/member.detail.store'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/entity-actions/manifests.ts new file mode 100644 index 0000000000..1926cca973 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/entity-actions/manifests.ts @@ -0,0 +1,24 @@ +import { UmbDeleteEntityAction } from '../../../../backoffice/shared/entity-actions/delete/delete.action'; +import { MEMBER_TYPES_REPOSITORY_ALIAS } from '../repository/manifests'; +import type { ManifestEntityAction } from '@umbraco-cms/models'; + +const entityType = 'member-type'; +const repositoryAlias = MEMBER_TYPES_REPOSITORY_ALIAS; + +const entityActions: Array = [ + { + type: 'entityAction', + alias: 'Umb.EntityAction.MemberType.Delete', + name: 'Delete Member Type Entity Action', + weight: 100, + meta: { + entityType, + icon: 'umb:trash', + label: 'Delete', + repositoryAlias, + api: UmbDeleteEntityAction, + }, + }, +]; + +export const manifests = [...entityActions]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/manifests.ts index a4edc8b4f1..cfe54ac0a3 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/manifests.ts @@ -1,5 +1,13 @@ import { manifests as sidebarMenuItemManifests } from './sidebar-menu-item/manifests'; import { manifests as treeManifests } from './tree/manifests'; +import { manifests as respositoryManifests } from './repository/manifests'; import { manifests as workspaceManifests } from './workspace/manifests'; +import { manifests as entityActionManifests } from './entity-actions/manifests'; -export const manifests = [...sidebarMenuItemManifests, ...treeManifests, ...workspaceManifests]; +export const manifests = [ + ...sidebarMenuItemManifests, + ...treeManifests, + ...respositoryManifests, + ...workspaceManifests, + ...entityActionManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/member-type.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/member-type.detail.store.ts deleted file mode 100644 index 25511b12e4..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/member-type.detail.store.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { MemberTypeDetails } from '@umbraco-cms/models'; -import { UmbContextToken } from '@umbraco-cms/context-api'; -import { ArrayState } from '@umbraco-cms/observable-api'; -import { UmbEntityDetailStore, UmbStoreBase } from '@umbraco-cms/store'; -import { UmbControllerHostInterface } from '@umbraco-cms/controller'; - - -export const UMB_MEMBER_TYPE_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbMemberTypeDetailStore'); - - -/** - * @export - * @class UmbMemberTypeDetailStore - * @extends {UmbStoreBase} - * @description - Details Data Store for Member Types - */ -export class UmbMemberTypeDetailStore extends UmbStoreBase implements UmbEntityDetailStore { - - - #data = new ArrayState([], (x) => x.key); - - - constructor(host: UmbControllerHostInterface) { - super(host, UMB_MEMBER_TYPE_DETAIL_STORE_CONTEXT_TOKEN.toString()); - } - - getScaffold(entityType: string, parentKey: string | null) { - return { - } as MemberTypeDetails; - } - - /** - * @description - Request a Data Type by key. The Data Type is added to the store and is returned as an Observable. - * @param {string} key - * @return {*} {(Observable)} - * @memberof UmbMemberTypesStore - */ - getByKey(key: string) { - return null as any; - } - - // TODO: make sure UI somehow can follow the status of this action. - /** - * @description - Save a Data Type. - * @param {Array} memberTypes - * @memberof UmbMemberTypesStore - * @return {*} {Promise} - */ - save(data: MemberTypeDetails[]) { - return null as any; - } - - // TODO: How can we avoid having this in both stores? - /** - * @description - Delete a Data Type. - * @param {string[]} keys - * @memberof UmbMemberTypesStore - * @return {*} {Promise} - */ - async delete(keys: string[]) { - // TODO: use backend cli when available. - return null as any; - - this.#data.remove(keys); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/member-type.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/member-type.tree.store.ts deleted file mode 100644 index e83c96d5f9..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/member-type.tree.store.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { EntityTreeItemModel, MemberTypeResource } from '@umbraco-cms/backend-api'; -import { tryExecuteAndNotify } from '@umbraco-cms/resources'; -import { UmbContextToken } from '@umbraco-cms/context-api'; -import { ArrayState } from '@umbraco-cms/observable-api'; -import { UmbStoreBase } from '@umbraco-cms/store'; -import type { UmbControllerHostInterface } from '@umbraco-cms/controller'; - -export const UMB_MEMBER_TYPE_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken( - 'UmbMemberTypeTreeStore' -); - -/** - * @export - * @class UmbMemberTypeTreeStore - * @extends {UmbStoreBase} - * @description - Tree Data Store for Member Types - */ -export class UmbMemberTypeTreeStore extends UmbStoreBase { - // TODO: use the right type here: - #data = new ArrayState([], (x) => x.key); - - constructor(host: UmbControllerHostInterface) { - super(host, UMB_MEMBER_TYPE_TREE_STORE_CONTEXT_TOKEN.toString()); - } - - // TODO: How can we avoid having this in both stores? - /** - * @description - Delete a Data Type. - * @param {string[]} keys - * @memberof UmbMemberTypesStore - * @return {*} {Promise} - */ - async delete(keys: string[]) { - // TODO: use backend cli when available. - await fetch('/umbraco/backoffice/member-type/delete', { - method: 'POST', - body: JSON.stringify(keys), - headers: { - 'Content-Type': 'application/json', - }, - }); - - this.#data.remove(keys); - } - - getTreeRoot() { - tryExecuteAndNotify(this._host, MemberTypeResource.getTreeMemberTypeRoot({})).then(({ data }) => { - if (data) { - // TODO: how do we handle if an item has been removed during this session(like in another tab or by another user)? - this.#data.append(data.items); - } - }); - - // TODO: remove ignore when we know how to handle trashed items. - return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === null)); - } - - getTreeItemChildren(key: string) { - /* - tryExecuteAndNotify( - this._host, - MemberTypeResource.getTreeMemberTypeChildren({ - parentKey: key, - }) - ).then(({ data }) => { - if (data) { - // TODO: how do we handle if an item has been removed during this session(like in another tab or by another user)? - this.#data.append(data.items); - } - }); - */ - - return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === key)); - } - - getTreeItems(keys: Array) { - if (keys?.length > 0) { - tryExecuteAndNotify( - this._host, - MemberTypeResource.getTreeMemberTypeItem({ - key: keys, - }) - ).then(({ data }) => { - if (data) { - // TODO: how do we handle if an item has been removed during this session(like in another tab or by another user)? - this.#data.append(data); - } - }); - } - - return this.#data.getObservablePart((items) => items.filter((item) => keys.includes(item.key ?? ''))); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/manifests.ts new file mode 100644 index 0000000000..deee635772 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/manifests.ts @@ -0,0 +1,13 @@ +import { UmbMemberTypeRepository } from './member-type.repository'; +import { ManifestRepository } from 'libs/extensions-registry/repository.models'; + +export const MEMBER_TYPES_REPOSITORY_ALIAS = 'Umb.Repository.MemberTypes'; + +const repository: ManifestRepository = { + type: 'repository', + alias: MEMBER_TYPES_REPOSITORY_ALIAS, + name: 'Member Types Repository', + class: UmbMemberTypeRepository, +}; + +export const manifests = [repository]; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.detail.store.ts new file mode 100644 index 0000000000..bba1a580fa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.detail.store.ts @@ -0,0 +1,33 @@ +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { UmbStoreBase } from '@umbraco-cms/store'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { ArrayState } from '@umbraco-cms/observable-api'; +import type { MemberTypeDetails } from '@umbraco-cms/models'; + +/** + * @export + * @class UmbMemberTypeDetailStore + * @extends {UmbStoreBase} + * @description - Details Data Store for Member Types + */ +export class UmbMemberTypeDetailStore + extends UmbStoreBase +{ + #data = new ArrayState([], (x) => x.key); + + constructor(host: UmbControllerHostInterface) { + super(host, UmbMemberTypeDetailStore.name); + } + + append(MemberType: MemberTypeDetails) { + this.#data.append([MemberType]); + } + + remove(uniques: string[]) { + this.#data.remove(uniques); + } +} + +export const UMB_MEMBER_TYPE_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken( + UmbMemberTypeDetailStore.name +); \ No newline at end of file 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 new file mode 100644 index 0000000000..d2512d0aa0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.repository.ts @@ -0,0 +1,200 @@ +import { MemberTypeTreeServerDataSource } from './sources/member-type.tree.server.data'; +import { UmbMemberTypeTreeStore, UMB_MEMBER_TYPE_TREE_STORE_CONTEXT_TOKEN } from './member-type.tree.store'; +import { UmbMemberTypeDetailStore, UMB_MEMBER_TYPE_DETAIL_STORE_CONTEXT_TOKEN } from './member-type.detail.store'; +import { UmbMemberTypeDetailServerDataSource } from './sources/member-type.detail.server.data'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { UmbContextConsumerController } from '@umbraco-cms/context-api'; +import { RepositoryTreeDataSource, UmbDetailRepository, UmbTreeRepository } from '@umbraco-cms/repository'; +import { ProblemDetailsModel } from '@umbraco-cms/backend-api'; +import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification'; +import type { MemberTypeDetails } from '@umbraco-cms/models'; + +// TODO => use correct type when available +type ItemType = any; + +export class UmbMemberTypeRepository implements UmbTreeRepository, UmbDetailRepository { + #init!: Promise; + + #host: UmbControllerHostInterface; + + #treeSource: RepositoryTreeDataSource; + #treeStore?: UmbMemberTypeTreeStore; + + #detailSource: UmbMemberTypeDetailServerDataSource; + #detailStore?: UmbMemberTypeDetailStore; + + #notificationService?: UmbNotificationService; + + constructor(host: UmbControllerHostInterface) { + this.#host = host; + + // TODO: figure out how spin up get the correct data source + this.#treeSource = new MemberTypeTreeServerDataSource(this.#host); + this.#detailSource = new UmbMemberTypeDetailServerDataSource(this.#host); + + this.#init = Promise.all([ + new UmbContextConsumerController(this.#host, UMB_MEMBER_TYPE_DETAIL_STORE_CONTEXT_TOKEN, (instance) => { + this.#detailStore = instance; + }), + + new UmbContextConsumerController(this.#host, UMB_MEMBER_TYPE_TREE_STORE_CONTEXT_TOKEN, (instance) => { + this.#treeStore = instance; + }), + + new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (instance) => { + this.#notificationService = instance; + }), + ]); + } + + async requestRootTreeItems() { + await this.#init; + + const { data, error } = await this.#treeSource.getRootItems(); + + if (data) { + this.#treeStore?.appendItems(data.items); + } + + return { data, error, asObservable: () => this.#treeStore!.rootItems }; + } + + async requestTreeItemsOf(parentKey: string | null) { + await this.#init; + + if (!parentKey) { + const error: ProblemDetailsModel = { title: 'Parent key is missing' }; + return { data: undefined, error }; + } + + const { data, error } = await this.#treeSource.getChildrenOf(parentKey); + + if (data) { + this.#treeStore?.appendItems(data.items); + } + + return { data, error, asObservable: () => this.#treeStore!.childrenOf(parentKey) }; + } + + async requestTreeItems(keys: Array) { + await this.#init; + + if (!keys) { + const error: ProblemDetailsModel = { title: 'Keys are missing' }; + return { data: undefined, error }; + } + + const { data, error } = await this.#treeSource.getItems(keys); + + return { data, error, asObservable: () => this.#treeStore!.items(keys) }; + } + + async rootTreeItems() { + await this.#init; + return this.#treeStore!.rootItems; + } + + async treeItemsOf(parentKey: string | null) { + await this.#init; + return this.#treeStore!.childrenOf(parentKey); + } + + async treeItems(keys: Array) { + await this.#init; + return this.#treeStore!.items(keys); + } + + // DETAILS + + async createDetailsScaffold() { + await this.#init; + return this.#detailSource.createDetailsScaffold(); + } + + async requestByKey(key: string) { + await this.#init; + + // TODO: should we show a notification if the key is missing? + // Investigate what is best for Acceptance testing, cause in that perspective a thrown error might be the best choice? + if (!key) { + const error: ProblemDetailsModel = { title: 'Key is missing' }; + return { error }; + } + const { data, error } = await this.#detailSource.requestByKey(key); + + if (data) { + this.#detailStore?.append(data); + } + return { data, error }; + } + + async delete(key: string) { + await this.#init; + + if (!key) { + const error: ProblemDetailsModel = { title: 'Key is missing' }; + return { error }; + } + + const { error } = await this.#detailSource.delete(key); + + if (!error) { + const notification = { data: { message: `Member type deleted` } }; + this.#notificationService?.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 deleted from the store while someone is editing it. + this.#detailStore?.remove([key]); + this.#treeStore?.removeItem(key); + // TODO: would be nice to align the stores on methods/methodNames. + + return { error }; + } + + async saveDetail(detail: ItemType) { + 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.key) { + const error: ProblemDetailsModel = { title: 'Member type is missing' }; + return { error }; + } + + const { error } = await this.#detailSource.saveDetail(detail); + + if (!error) { + const notification = { data: { message: `Member type '${detail.name}' saved` } }; + this.#notificationService?.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.#detailStore?.append(detail); + this.#treeStore?.updateItem(detail.key, { name: detail.name }); + // TODO: would be nice to align the stores on methods/methodNames. + + return { error }; + } + + async createDetail(detail: MemberTypeDetails) { + await this.#init; + + if (!detail.name) { + const error: ProblemDetailsModel = { title: 'Name is missing' }; + return { error }; + } + + const { data, error } = await this.#detailSource.createDetail(detail); + + if (!error) { + const notification = { data: { message: `Member type '${detail.name}' created` } }; + this.#notificationService?.peek('positive', notification); + } + + return { data, error }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.tree.store.ts new file mode 100644 index 0000000000..7e3322625d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/member-type.tree.store.ts @@ -0,0 +1,20 @@ +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { UmbTreeStoreBase } from '@umbraco-cms/store'; +import type { UmbControllerHostInterface } from '@umbraco-cms/controller'; + +/** + * @export + * @class UmbMemberTypeTreeStore + * @extends {UmbStoreBase} + * @description - Tree Data Store for Member Types + */ +export class UmbMemberTypeTreeStore extends UmbTreeStoreBase { + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_MEMBER_TYPE_TREE_STORE_CONTEXT_TOKEN.toString()); + } +} + +export const UMB_MEMBER_TYPE_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken( + UmbMemberTypeTreeStore.name +); 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 new file mode 100644 index 0000000000..01f2f5537a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/sources/member-type.detail.server.data.ts @@ -0,0 +1,116 @@ +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; +import { ProblemDetailsModel } from '@umbraco-cms/backend-api'; +import type { MemberTypeDetails } from '@umbraco-cms/models'; +import { UmbDetailRepository } from '@umbraco-cms/repository'; + +/** + * @description - A data source for the MemberType detail that fetches data from the server + * @export + * @class UmbMemberTypeDetailServerDataSource + * @implements {MemberTypeDetailDataSource} + */ +export class UmbMemberTypeDetailServerDataSource implements UmbDetailRepository { + #host: UmbControllerHostInterface; + + constructor(host: UmbControllerHostInterface) { + this.#host = host; + } + + /** + * @description - Creates a new MemberType scaffold + * @return {*} + * @memberof UmbMemberTypeDetailServerDataSource + */ + async createDetailsScaffold() { + const data = {} as MemberTypeDetails; + return { data }; + } + + /** + * @description - Fetches a MemberType with the given key from the server + * @param {string} key + * @return {*} + * @memberof UmbMemberTypeDetailServerDataSource + */ + requestByKey(key: string) { + //return tryExecuteAndNotify(this.#host, MemberTypeResource.getMemberTypeByKey({ key })); + // TODO => use backend cli when available. + return tryExecuteAndNotify(this.#host, fetch(`/umbraco/management/api/v1/member-group/${key}`)) as any; + } + + /** + * @description - Updates a MemberType on the server + * @param {MemberTypeDetails} memberType + * @return {*} + * @memberof UmbMemberTypeDetailServerDataSource + */ + async saveDetail(memberType: MemberTypeDetails) { + if (!memberType.key) { + const error: ProblemDetailsModel = { title: 'MemberType key is missing' }; + return { error }; + } + + const payload = { key: memberType.key, requestBody: memberType }; + //return tryExecuteAndNotify(this.#host, MemberTypeResource.putMemberTypeByKey(payload)); + + // TODO => use backend cli when available. + return tryExecuteAndNotify( + this.#host, + fetch(`/umbraco/management/api/v1/member-type/${memberType.key}`, { + method: 'PUT', + body: JSON.stringify(payload), + headers: { + 'Content-Type': 'application/json', + }, + }) + ) as any; + } + + /** + * @description - Inserts a new MemberType on the server + * @param {MemberTypeDetails} data + * @return {*} + * @memberof UmbMemberTypeDetailServerDataSource + */ + async createDetail(data: MemberTypeDetails) { + const requestBody = { + name: data.name, + }; + + //return tryExecuteAndNotify(this.#host, MemberTypeResource.postMemberType({ requestBody })); + // TODO => use backend cli when available. + return tryExecuteAndNotify( + this.#host, + fetch(`/umbraco/management/api/v1/member-type`, { + method: 'POST', + body: JSON.stringify(requestBody), + headers: { + 'Content-Type': 'application/json', + }, + }) + ) as any; + } + + /** + * @description - Deletes a MemberType on the server + * @param {string} key + * @return {*} + * @memberof UmbMemberTypeDetailServerDataSource + */ + async delete(key: string) { + if (!key) { + const error: ProblemDetailsModel = { title: 'Key is missing' }; + return { error }; + } + + //return await tryExecuteAndNotify(this.#host, MemberTypeResource.deleteMemberTypeByKey({ key })); + // TODO => use backend cli when available. + return tryExecuteAndNotify( + this.#host, + fetch(`/umbraco/management/api/v1/member-type/${key}`, { + method: 'DELETE', + }) + ) as any; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/sources/member-type.tree.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/sources/member-type.tree.server.data.ts new file mode 100644 index 0000000000..8e147af77d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/repository/sources/member-type.tree.server.data.ts @@ -0,0 +1,63 @@ +import { MemberTypeResource, ProblemDetailsModel } from '@umbraco-cms/backend-api'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { RepositoryTreeDataSource } from '@umbraco-cms/repository'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; + +/** + * A data source for the MemberType tree that fetches data from the server + * @export + * @class MemberTypeTreeServerDataSource + * @implements {MemberTypeTreeDataSource} + */ +export class MemberTypeTreeServerDataSource implements RepositoryTreeDataSource { + #host: UmbControllerHostInterface; + + /** + * Creates an instance of MemberTypeTreeDataSource. + * @param {UmbControllerHostInterface} host + * @memberof MemberTypeTreeDataSource + */ + constructor(host: UmbControllerHostInterface) { + this.#host = host; + } + + /** + * Fetches the root items for the tree from the server + * @return {*} + * @memberof MemberTypeTreeServerDataSource + */ + async getRootItems() { + return tryExecuteAndNotify(this.#host, MemberTypeResource.getTreeMemberTypeRoot({})); + } + + /** + * Fetches the children of a given parent key from the server + * @param {(string | null)} parentKey + * @return {*} + * @memberof MemberTypeTreeServerDataSource + */ + async getChildrenOf(parentKey: string | null) { + const error: ProblemDetailsModel = { title: 'Not implemented for Member Type' }; + return { error }; + } + + /** + * Fetches the items for the given keys from the server + * @param {Array} keys + * @return {*} + * @memberof MemberTypeTreeServerDataSource + */ + async getItems(keys: Array) { + if (!keys || keys.length === 0) { + const error: ProblemDetailsModel = { title: 'Keys are missing' }; + return { error }; + } + + return tryExecuteAndNotify( + this.#host, + MemberTypeResource.getTreeMemberTypeItem({ + key: keys, + }) + ); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/tree/manifests.ts index fd0030ad39..6aca4cf797 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/tree/manifests.ts @@ -1,4 +1,4 @@ -import { UMB_MEMBER_TYPE_TREE_STORE_CONTEXT_TOKEN } from '../member-type.tree.store'; +import { UmbMemberTypeRepository } from '../repository/member-type.repository'; import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models'; const treeAlias = 'Umb.Tree.MemberTypes'; @@ -8,7 +8,7 @@ const tree: ManifestTree = { alias: treeAlias, name: 'Member Types Tree', meta: { - storeAlias: UMB_MEMBER_TYPE_TREE_STORE_CONTEXT_TOKEN.toString(), + repository: UmbMemberTypeRepository }, }; 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 new file mode 100644 index 0000000000..5f076fa40b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.context.ts @@ -0,0 +1,80 @@ +import { UmbWorkspaceContext } from '../../../shared/components/workspace/workspace-context/workspace-context'; +import { UmbWorkspaceEntityContextInterface } from '../../../shared/components/workspace/workspace-context/workspace-entity-context.interface'; +import { UmbMemberTypeRepository } from '../repository/member-type.repository'; +import { ObjectState } from '@umbraco-cms/observable-api'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; + +// TODO => use correct tpye +type EntityType = any; + +export class UmbWorkspaceMemberTypeContext + extends UmbWorkspaceContext + implements UmbWorkspaceEntityContextInterface +{ + #isNew = false; + #host: UmbControllerHostInterface; + #dataTypeRepository: UmbMemberTypeRepository; + + #data = new ObjectState(undefined); + name = this.#data.getObservablePart((data) => data?.name); + + constructor(host: UmbControllerHostInterface) { + super(host); + this.#host = host; + this.#dataTypeRepository = new UmbMemberTypeRepository(this.#host); + } + + async load(entityKey: string) { + const { data } = await this.#dataTypeRepository.requestByKey(entityKey); + if (data) { + this.#isNew = false; + this.#data.next(data); + } + } + + async createScaffold() { + const { data } = await this.#dataTypeRepository.createDetailsScaffold(); + if (!data) return; + this.#isNew = true; + this.#data.next(data); + } + + getData() { + return this.#data.getValue(); + } + + getEntityKey() { + return this.getData()?.key || ''; + } + + getEntityType() { + return 'member-type'; + } + + setName(name: string) { + this.#data.update({ name }); + } + + setPropertyValue(alias: string, value: unknown) { + // Not implemented + } + + async save() { + if (!this.#data.value) return; + if (this.#isNew) { + await this.#dataTypeRepository.createDetail(this.#data.value); + } else { + await this.#dataTypeRepository.saveDetail(this.#data.value); + } + // If it went well, then its not new anymore?. + this.#isNew = false; + } + + async delete(key: string) { + await this.#dataTypeRepository.delete(key); + } + + public destroy(): void { + this.#data.complete(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.element.ts index 0f80905556..2ff6e33769 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/workspace/member-type-workspace.element.ts @@ -1,11 +1,12 @@ +import { UUIInputEvent, UUIInputElement } from '@umbraco-ui/uui'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement } from 'lit'; -import { customElement, property } from 'lit/decorators.js'; - -import '../../../shared/components/workspace/workspace-layout/workspace-layout.element'; +import { css, html } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; +import { UmbWorkspaceMemberTypeContext } from './member-type-workspace.context'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-member-type-workspace') -export class UmbMemberTypeWorkspaceElement extends LitElement { +export class UmbMemberTypeWorkspaceElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -14,14 +15,61 @@ export class UmbMemberTypeWorkspaceElement extends LitElement { width: 100%; height: 100%; } + + #header { + /* TODO: can this be applied from layout slot CSS? */ + margin: 0 var(--uui-size-layout-1); + flex: 1 1 auto; + } `, ]; - @property() - id!: string; + + + @state() + private _memberTypeName = ''; + + @state() + private _unique?: string; + + #workspaceContext: UmbWorkspaceMemberTypeContext = new UmbWorkspaceMemberTypeContext(this); + + public load(entityKey: string) { + this.#workspaceContext?.load(entityKey); + this._unique = entityKey; + } + + public create() { + this.#workspaceContext.createScaffold(); + } + + constructor() { + super(); + this.provideContext('umbWorkspaceContext', this.#workspaceContext); + this.observe(this.#workspaceContext.name, (memberTypeName) => { + if (memberTypeName !== this._memberTypeName) { + this._memberTypeName = memberTypeName ?? ''; + } + }); + } + + // TODO. find a way where we don't have to do this for all Workspaces. + private _handleInput(event: UUIInputEvent) { + if (event instanceof UUIInputEvent) { + const target = event.composedPath()[0] as UUIInputElement; + + if (typeof target?.value === 'string') { + this.#workspaceContext.setName(target.value); + } + } + } render() { - return html` Member Type Workspace `; + return html` + + + + `; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/manifests.ts index 3449a3787a..b83f66480b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/manifests.ts @@ -1,5 +1,6 @@ import { UmbDeleteEntityAction } from '../../../../backoffice/shared/entity-actions/delete/delete.action'; import { UmbMoveEntityAction } from '../../../../backoffice/shared/entity-actions/move/move.action'; +import { DICTIONARY_REPOSITORY_ALIAS } from '../repository/manifests'; import UmbReloadDictionaryEntityAction from './reload.action'; import UmbImportDictionaryEntityAction from './import/import.action'; import UmbExportDictionaryEntityAction from './export/export.action'; @@ -7,7 +8,7 @@ import UmbCreateDictionaryEntityAction from './create/create.action'; import type { ManifestEntityAction } from '@umbraco-cms/models'; const entityType = 'dictionary-item'; -const repositoryAlias = 'Umb.Repository.Dictionary'; +const repositoryAlias = DICTIONARY_REPOSITORY_ALIAS; const entityActions: Array = [ {