split tree repo for member groups

This commit is contained in:
Mads Rasmussen
2023-11-16 20:39:58 +01:00
parent 8db2a4af7a
commit 4b1958e441
32 changed files with 266 additions and 748 deletions

View File

@@ -1,9 +1,8 @@
import type { MemberGroupDetails } from '../../packages/members/member-groups/types.js';
import { UmbEntityData } from './entity.data.js';
import { createEntityTreeItem } from './utils.js';
import { EntityTreeItemResponseModel, PagedEntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
export const data: Array<MemberGroupDetails> = [
export const data: Array<any> = [
{
name: 'Member Group AAA',
type: 'member-group',

View File

@@ -1,4 +1,5 @@
import { MEMBER_GROUP_REPOSITORY_ALIAS } from '../repository/manifests.js';
import { UMB_MEMBER_GROUP_DETAIL_REPOSITORY_ALIAS } from '../repository/index.js';
import { UMB_MEMBER_GROUP_ENTITY_TYPE } from '../entity.js';
import type { ManifestEntityAction } from '@umbraco-cms/backoffice/extension-registry';
import { UmbDeleteEntityAction } from '@umbraco-cms/backoffice/entity-action';
@@ -11,8 +12,8 @@ const entityActions: Array<ManifestEntityAction> = [
meta: {
icon: 'icon-trash',
label: 'Delete',
repositoryAlias: MEMBER_GROUP_REPOSITORY_ALIAS,
entityTypes: ['member-group'],
repositoryAlias: UMB_MEMBER_GROUP_DETAIL_REPOSITORY_ALIAS,
entityTypes: [UMB_MEMBER_GROUP_ENTITY_TYPE],
},
},
];

View File

@@ -0,0 +1,2 @@
export const UMB_MEMBER_GROUP_ROOT_ENTITY_TYPE = 'member-group-root';
export const UMB_MEMBER_GROUP_ENTITY_TYPE = 'member-group';

View File

@@ -0,0 +1 @@
export * from './repository/index.js';

View File

@@ -1,16 +1,18 @@
import { UMB_MEMBER_MENU_ALIAS } from '../../menu.manifests.js';
import { UMB_MEMBER_GROUP_TREE_ALIAS } from '../tree/index.js';
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
const menuItem: ManifestTypes = {
type: 'menuItem',
kind: 'tree',
alias: 'Umb.MenuItem.MemberGroups',
name: 'Member Groups Menu Item',
weight: 800,
alias: 'Umb.MenuItem.MemberGroup',
name: 'Member Group Menu Item',
weight: 400,
meta: {
label: 'Member Groups',
icon: 'icon-folder',
treeAlias: 'Umb.Tree.MemberGroups',
menus: ['Umb.Menu.Members'],
treeAlias: UMB_MEMBER_GROUP_TREE_ALIAS,
menus: [UMB_MEMBER_MENU_ALIAS],
},
};

View File

@@ -0,0 +1,2 @@
export { UmbMemberGroupDetailRepository } from './member-group-detail.repository.js';
export { UMB_MEMBER_GROUP_DETAIL_REPOSITORY_ALIAS } from './manifests.js';

View File

@@ -0,0 +1,22 @@
import { UmbMemberGroupDetailRepository } from './member-group-detail.repository.js';
import { UmbMemberGroupDetailStore } from './member-group-detail.store.js';
import type { ManifestRepository, ManifestStore } from '@umbraco-cms/backoffice/extension-registry';
export const UMB_MEMBER_GROUP_DETAIL_REPOSITORY_ALIAS = 'Umb.Repository.MemberGroup.Detail';
export const UMB_MEMBER_GROUP_DETAIL_STORE_ALIAS = 'Umb.Store.MemberGroup.Detail';
const repository: ManifestRepository = {
type: 'repository',
alias: UMB_MEMBER_GROUP_DETAIL_REPOSITORY_ALIAS,
name: 'MemberGroup Detail Detail Repository',
api: UmbMemberGroupDetailRepository,
};
const store: ManifestStore = {
type: 'store',
alias: UMB_MEMBER_GROUP_DETAIL_STORE_ALIAS,
name: 'MemberGroup Detail Store',
api: UmbMemberGroupDetailStore,
};
export const manifests = [repository, store];

View File

@@ -0,0 +1,9 @@
import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
export class UmbMemberGroupDetailRepository extends UmbRepositoryBase {
constructor(host: UmbControllerHost) {
super(host);
console.log('UmbMemberGroupDetailRepository');
}
}

View File

@@ -0,0 +1,25 @@
import type { UmbMemberGroupDetailModel } from '../../types.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { UmbStoreBase } from '@umbraco-cms/backoffice/store';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
/**
* @export
* @class UmbMemberGroupDetailStore
* @extends {UmbStoreBase}
* @description - Data Store for MemberGroup Detail
*/
export class UmbMemberGroupDetailStore extends UmbStoreBase {
constructor(host: UmbControllerHostElement) {
super(
host,
UMB_MEMBER_GROUP_DETAIL_STORE_CONTEXT.toString(),
new UmbArrayState<UmbMemberGroupDetailModel>([], (x) => x.id),
);
}
}
export const UMB_MEMBER_GROUP_DETAIL_STORE_CONTEXT = new UmbContextToken<UmbMemberGroupDetailStore>(
'UmbMemberGroupDetailStore',
);

View File

@@ -0,0 +1 @@
export { UmbMemberGroupDetailRepository, UMB_MEMBER_GROUP_DETAIL_REPOSITORY_ALIAS } from './detail/index.js';

View File

@@ -1,32 +1,3 @@
import { UmbMemberGroupRepository } from './member-group.repository.js';
import { UmbMemberGroupStore } from './member-group.store.js';
import { UmbMemberGroupTreeStore } from './member-group.tree.store.js';
import type { ManifestStore, ManifestTreeStore, ManifestRepository } from '@umbraco-cms/backoffice/extension-registry';
import { manifests as detailManifests } from './detail/manifests.js';
export const MEMBER_GROUP_REPOSITORY_ALIAS = 'Umb.Repository.MemberGroup';
const repository: ManifestRepository = {
type: 'repository',
alias: MEMBER_GROUP_REPOSITORY_ALIAS,
name: 'Member Group Repository',
api: UmbMemberGroupRepository,
};
export const MEMBER_GROUP_STORE_ALIAS = 'Umb.Store.MemberGroup';
export const MEMBER_GROUP_TREE_STORE_ALIAS = 'Umb.Store.MemberGroupTree';
const store: ManifestStore = {
type: 'store',
alias: MEMBER_GROUP_STORE_ALIAS,
name: 'Member Group Store',
api: UmbMemberGroupStore,
};
const treeStore: ManifestTreeStore = {
type: 'treeStore',
alias: MEMBER_GROUP_TREE_STORE_ALIAS,
name: 'Member Group Tree Store',
api: UmbMemberGroupTreeStore,
};
export const manifests = [store, treeStore, repository];
export const manifests = [...detailManifests];

View File

@@ -1,191 +0,0 @@
import type { MemberGroupDetails } from '../types.js';
import { UmbMemberGroupTreeStore, UMB_MEMBER_GROUP_TREE_STORE_CONTEXT_TOKEN } from './member-group.tree.store.js';
import { UmbMemberGroupDetailServerDataSource } from './sources/member-group.detail.server.data.js';
import { UmbMemberGroupStore, UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN } from './member-group.store.js';
import { UmbMemberGroupTreeServerDataSource } from './sources/member-group.tree.server.data.js';
import { UmbBaseController, type UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification';
import { UmbDetailRepository } from '@umbraco-cms/backoffice/repository';
import type { UmbTreeRepository, UmbTreeDataSource } from '@umbraco-cms/backoffice/tree';
import { EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbApi } from '@umbraco-cms/backoffice/extension-api';
// TODO => Update type when backend updated
export class UmbMemberGroupRepository
extends UmbBaseController
implements UmbTreeRepository<EntityTreeItemResponseModel>, UmbDetailRepository<any, any, any, any>, UmbApi
{
#init!: Promise<unknown>;
#treeSource: UmbTreeDataSource;
#treeStore?: UmbMemberGroupTreeStore;
#detailSource: UmbMemberGroupDetailServerDataSource;
#store?: UmbMemberGroupStore;
#notificationContext?: UmbNotificationContext;
constructor(host: UmbControllerHost) {
super(host);
// TODO: figure out how spin up get the correct data source
this.#treeSource = new UmbMemberGroupTreeServerDataSource(this);
this.#detailSource = new UmbMemberGroupDetailServerDataSource(this);
this.consumeContext(UMB_MEMBER_GROUP_TREE_STORE_CONTEXT_TOKEN, (instance) => {
this.#treeStore = instance;
});
this.consumeContext(UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN, (instance) => {
this.#store = instance;
});
this.consumeContext(UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => {
this.#notificationContext = instance;
});
}
// TREE:
async requestTreeRoot() {
await this.#init;
const data = {
id: null,
type: 'member-group-root',
name: 'Member Groups',
icon: 'icon-folder',
hasChildren: true,
};
return { data };
}
async requestRootTreeItems() {
await this.#init;
const { data, error } = await this.#treeSource.getRootItems();
if (data) {
this.#treeStore?.appendItems(data.items);
}
return { data, error };
}
async requestTreeItemsOf(parentId: string | null) {
return { data: undefined, error: { title: 'Not implemented', message: 'Not implemented' } };
}
async requestItemsLegacy(ids: Array<string>) {
await this.#init;
if (!ids) {
throw new Error('Ids are missing');
}
const { data, error } = await this.#treeSource.getItems(ids);
return { data, error };
}
async rootTreeItems() {
await this.#init;
return this.#treeStore!.rootItems;
}
async treeItemsOf(parentId: string | null) {
await this.#init;
return this.#treeStore!.childrenOf(parentId);
}
async itemsLegacy(ids: Array<string>) {
await this.#init;
return this.#treeStore!.items(ids);
}
// DETAIL
async createScaffold() {
await this.#init;
return this.#detailSource.createScaffold();
}
async requestById(id: string) {
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) {
throw new Error('Id is missing');
}
const { data, error } = await this.#detailSource.read(id);
if (data) {
this.#store?.append(data);
}
return { data, error };
}
async byId(id: string) {
if (!id) throw new Error('Id is missing');
await this.#init;
return this.#store!.byId(id);
}
async create(detail: MemberGroupDetails) {
await this.#init;
if (!detail.name) {
throw new Error('Name is missing');
}
const { data, error } = await this.#detailSource.create(detail);
if (!error) {
const notification = { data: { message: `Member group '${detail.name}' created` } };
this.#notificationContext?.peek('positive', notification);
}
return { data, error };
}
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;
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);
}
return { error };
}
async delete(id: string) {
if (!id) throw new Error('Id is missing');
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);
}
return { error };
}
}

View File

@@ -1,42 +0,0 @@
import type { MemberGroupDetails } from '../types.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UmbStoreBase } from '@umbraco-cms/backoffice/store';
/**
* @export
* @class UmbMemberGroupStore
* @extends {UmbStoreBase}
* @description - Data Store for Member Groups
*/
export class UmbMemberGroupStore extends UmbStoreBase {
#data = new UmbArrayState<MemberGroupDetails>([], (x) => x.id);
constructor(host: UmbControllerHostElement) {
super(
host,
UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN.toString(),
new UmbArrayState<MemberGroupDetails>([], (x) => x.id)
);
}
append(memberGroup: MemberGroupDetails) {
this._data.append([memberGroup]);
}
/**
* Retrieve a member from the store
* @param {string} id
* @memberof UmbMemberGroupStore
*/
byId(id: MemberGroupDetails['id']) {
return this._data.asObservablePart((x) => x.find((y) => y.id === id));
}
remove(uniques: string[]) {
this._data.remove(uniques);
}
}
export const UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMemberGroupStore>('UmbMemberGroupStore');

View File

@@ -1,126 +0,0 @@
import type { MemberGroupDetails } from '../../types.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
import { UmbDataSource } from '@umbraco-cms/backoffice/repository';
/**
* @description - A data source for the MemberGroup detail that fetches data from the server
* @export
* @class UmbMemberGroupDetailServerDataSource
* @implements {MemberGroupDetailDataSource}
*/
// TODO => Provide type when it is available
export class UmbMemberGroupDetailServerDataSource implements UmbDataSource<any, any, any, any> {
#host: UmbControllerHost;
constructor(host: UmbControllerHost) {
this.#host = host;
}
/**
* @description - Creates a new MemberGroup scaffold
* @return {*}
* @memberof UmbMemberGroupDetailServerDataSource
*/
async createScaffold() {
const data: MemberGroupDetails = {
name: '',
} as MemberGroupDetails;
return { data };
}
/**
* @description - Fetches a MemberGroup with the given id from the server
* @param {string} id
* @return {*}
* @memberof UmbMemberGroupDetailServerDataSource
*/
read(id: string) {
//return tryExecuteAndNotify(this.#host, MemberGroupResource.getMemberGroup({ id })) as any;
// TODO: use backend cli when available.
return tryExecuteAndNotify(this.#host, fetch(`/umbraco/management/api/v1/member-group/${id}`)) as any;
}
/**
* @description - Updates a MemberGroup on the server
* @param {MemberGroupDetails} memberGroup
* @return {*}
* @memberof UmbMemberGroupDetailServerDataSource
*/
async update(id: string, memberGroup: MemberGroupDetails) {
if (!memberGroup.id) {
throw new Error('Member Group id is missing');
}
const payload = { id: memberGroup.id, requestBody: memberGroup };
//return tryExecuteAndNotify(this.#host, MemberGroupResource.putMemberGroupByKey(payload));
// TODO: use backend cli when available.
return tryExecuteAndNotify(
this.#host,
fetch(`/umbraco/management/api/v1/member-group/${memberGroup.id}`, {
method: 'PUT',
body: JSON.stringify(payload),
headers: {
'Content-Type': 'application/json',
},
}),
) as any;
}
/**
* @description - Inserts a new MemberGroup on the server
* @param {MemberGroupDetails} data
* @return {*}
* @memberof UmbMemberGroupDetailServerDataSource
*/
async create(data: MemberGroupDetails) {
const requestBody = {
name: data.name,
};
//return tryExecuteAndNotify(this.#host, MemberGroupResource.postMemberGroup({ requestBody }));
// TODO: use backend cli when available.
return tryExecuteAndNotify(
this.#host,
fetch(`/umbraco/management/api/v1/member-group/`, {
method: 'POST',
body: JSON.stringify(requestBody),
headers: {
'Content-Type': 'application/json',
},
}),
) as any;
}
/**
* @description - Deletes a MemberGroup on the server
* @param {string} id
* @return {*}
* @memberof UmbMemberGroupDetailServerDataSource
*/
async trash(id: string) {
return this.delete(id);
}
/**
* @description - Deletes a MemberGroup on the server
* @param {string} id
* @return {*}
* @memberof UmbMemberGroupDetailServerDataSource
*/
async delete(id: string) {
if (!id) {
throw new Error('Id is missing');
}
//return await tryExecuteAndNotify(this.#host, MemberGroupResource.deleteMemberGroupByKey({ id }));
// TODO: use backend cli when available.
return tryExecuteAndNotify(
this.#host,
fetch(`/umbraco/management/api/v1/member-group/${id}`, {
method: 'DELETE',
}),
) as any;
}
}

View File

@@ -1,62 +0,0 @@
import { MemberGroupResource } from '@umbraco-cms/backoffice/backend-api';
import { type UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { type UmbTreeDataSource } from '@umbraco-cms/backoffice/tree';
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
/**
* A data source for the Member Group tree that fetches data from the server
* @export
* @class UmbMemberGroupTreeServerDataSource
* @implements {UmbTreeDataSource}
*/
export class UmbMemberGroupTreeServerDataSource implements UmbTreeDataSource {
#host: UmbControllerHost;
/**
* Creates an instance of UmbMemberGroupTreeServerDataSource.
* @param {UmbControllerHost} host
* @memberof UmbMemberGroupTreeServerDataSource
*/
constructor(host: UmbControllerHost) {
this.#host = host;
}
/**
* Fetches the root items for the tree from the server
* @return {*}
* @memberof UmbMemberGroupTreeServerDataSource
*/
async getRootItems() {
return tryExecuteAndNotify(this.#host, MemberGroupResource.getTreeMemberGroupRoot({}));
}
/**
* Fetches the children of a given parent id from the server
* @param {(string | null)} parentId
* @return {*}
* @memberof UmbMemberGroupTreeServerDataSource
*/
async getChildrenOf(parentId: string | null) {
// Not implemented for this tree
return {};
}
/**
* Fetches the items for the given ids from the server
* @param {Array<string>} ids
* @return {*}
* @memberof UmbMemberGroupTreeServerDataSource
*/
async getItems(ids: Array<string>) {
if (!ids || ids.length === 0) {
throw new Error('Ids are missing');
}
return tryExecuteAndNotify(
this.#host,
MemberGroupResource.getMemberGroupItem({
id: ids,
}),
);
}
}

View File

@@ -0,0 +1,9 @@
export { UmbMemberGroupTreeRepository } from './member-group-tree.repository.js';
export {
UMB_MEMBER_GROUP_TREE_REPOSITORY_ALIAS,
UMB_MEMBER_GROUP_TREE_STORE_ALIAS,
UMB_MEMBER_GROUP_TREE_ALIAS,
} from './manifests.js';
export { UMB_MEMBER_GROUP_TREE_STORE_CONTEXT } from './member-group-tree.store.js';
export { type UmbMemberGroupTreeStore } from './member-group-tree.store.js';
export * from './types.js';

View File

@@ -1,15 +1,37 @@
import { MEMBER_GROUP_REPOSITORY_ALIAS } from '../repository/manifests.js';
import type { ManifestTree, ManifestTreeItem } from '@umbraco-cms/backoffice/extension-registry';
import { UMB_MEMBER_GROUP_ENTITY_TYPE, UMB_MEMBER_GROUP_ROOT_ENTITY_TYPE } from '../entity.js';
import { UmbMemberGroupTreeRepository } from './member-group-tree.repository.js';
import { UmbMemberGroupTreeStore } from './member-group-tree.store.js';
import type {
ManifestRepository,
ManifestTree,
ManifestTreeItem,
ManifestTreeStore,
} from '@umbraco-cms/backoffice/extension-registry';
const treeAlias = 'Umb.Tree.MemberGroups';
export const UMB_MEMBER_GROUP_TREE_REPOSITORY_ALIAS = 'Umb.Repository.MemberGroup.Tree';
export const UMB_MEMBER_GROUP_TREE_STORE_ALIAS = 'Umb.Store.MemberGroup.Tree';
export const UMB_MEMBER_GROUP_TREE_ALIAS = 'Umb.Tree.MemberGroup';
const treeRepository: ManifestRepository = {
type: 'repository',
alias: UMB_MEMBER_GROUP_TREE_REPOSITORY_ALIAS,
name: 'MemberGroup Tree Repository',
api: UmbMemberGroupTreeRepository,
};
const treeStore: ManifestTreeStore = {
type: 'treeStore',
alias: UMB_MEMBER_GROUP_TREE_STORE_ALIAS,
name: 'MemberGroup Tree Store',
api: UmbMemberGroupTreeStore,
};
const tree: ManifestTree = {
type: 'tree',
alias: treeAlias,
name: 'Member Groups Tree',
weight: 100,
alias: UMB_MEMBER_GROUP_TREE_ALIAS,
name: 'MemberGroup Tree',
meta: {
repositoryAlias: MEMBER_GROUP_REPOSITORY_ALIAS,
repositoryAlias: UMB_MEMBER_GROUP_TREE_REPOSITORY_ALIAS,
},
};
@@ -17,10 +39,10 @@ const treeItem: ManifestTreeItem = {
type: 'treeItem',
kind: 'entity',
alias: 'Umb.TreeItem.MemberGroup',
name: 'Member Group Tree Item',
name: 'MemberGroup Tree Item',
meta: {
entityTypes: ['member-group-root', 'member-group'],
entityTypes: [UMB_MEMBER_GROUP_ROOT_ENTITY_TYPE, UMB_MEMBER_GROUP_ENTITY_TYPE],
},
};
export const manifests = [tree, treeItem];
export const manifests = [treeRepository, treeStore, tree, treeItem];

View File

@@ -0,0 +1,28 @@
import { UMB_MEMBER_GROUP_ROOT_ENTITY_TYPE } from '../entity.js';
import { UmbMemberGroupTreeServerDataSource } from './member-group-tree.server.data-source.js';
import { UmbMemberGroupTreeItemModel, UmbMemberGroupTreeRootModel } from './types.js';
import { UMB_MEMBER_GROUP_TREE_STORE_CONTEXT } from './member-group-tree.store.js';
import { UmbTreeRepositoryBase } from '@umbraco-cms/backoffice/tree';
import { type UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbApi } from '@umbraco-cms/backoffice/extension-api';
export class UmbMemberGroupTreeRepository
extends UmbTreeRepositoryBase<UmbMemberGroupTreeItemModel, UmbMemberGroupTreeRootModel>
implements UmbApi
{
constructor(host: UmbControllerHost) {
super(host, UmbMemberGroupTreeServerDataSource, UMB_MEMBER_GROUP_TREE_STORE_CONTEXT);
}
async requestTreeRoot() {
const data = {
id: null,
type: UMB_MEMBER_GROUP_ROOT_ENTITY_TYPE,
name: 'Member Groups',
icon: 'icon-folder',
hasChildren: true,
};
return { data };
}
}

View File

@@ -0,0 +1,61 @@
import type { UmbTreeDataSource } from '@umbraco-cms/backoffice/tree';
import { EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
/**
* A data source for the MemberGroup tree that fetches data from the server
* @export
* @class UmbMemberGroupTreeServerDataSource
* @implements {UmbTreeDataSource}
*/
export class UmbMemberGroupTreeServerDataSource implements UmbTreeDataSource<EntityTreeItemResponseModel> {
#host: UmbControllerHost;
/**
* Creates an instance of UmbMemberGroupTreeServerDataSource.
* @param {UmbControllerHost} host
* @memberof UmbMemberGroupTreeServerDataSource
*/
constructor(host: UmbControllerHost) {
this.#host = host;
}
/**
* Fetches the root items for the tree from the server
* @return {*}
* @memberof UmbMemberGroupTreeServerDataSource
*/
async getRootItems(): Promise<any> {
alert('not implemented');
//return tryExecuteAndNotify(this.#host, MemberGroupResource.getTreeMemberGroupRoot({}));
}
/**
* Fetches the children of a given parent id from the server
* @param {(string)} parentId
* @return {*}
* @memberof UmbMemberGroupTreeServerDataSource
*/
async getChildrenOf(parentId: string | null): Promise<any> {
alert('not implemented');
/* TODO: should we make getRootItems() internal
so it only is a server concern that there are two endpoints? */
/*
if (parentId === null) {
return this.getRootItems();
} else {
return tryExecuteAndNotify(
this.#host,
MemberGroupResource.getTreeMemberGroupChildren({
parentId,
}),
);
}
*/
}
// TODO: remove when interface is cleaned up
async getItems(unique: Array<string>): Promise<any> {
throw new Error('Dot not use this method. Use the item source instead');
}
}

View File

@@ -1,12 +1,12 @@
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UmbEntityTreeStore } from '@umbraco-cms/backoffice/tree';
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
/**
* @export
* @class UmbMemberGroupTreeStore
* @extends {UmbEntityTreeStore}
* @description - Tree Data Store for Member Groups
* @extends {UmbStoreBase}
* @description - Tree Data Store for MemberGroup Items
*/
export class UmbMemberGroupTreeStore extends UmbEntityTreeStore {
/**
@@ -15,10 +15,10 @@ export class UmbMemberGroupTreeStore extends UmbEntityTreeStore {
* @memberof UmbMemberGroupTreeStore
*/
constructor(host: UmbControllerHostElement) {
super(host, UMB_MEMBER_GROUP_TREE_STORE_CONTEXT_TOKEN.toString());
super(host, UMB_MEMBER_GROUP_TREE_STORE_CONTEXT.toString());
}
}
export const UMB_MEMBER_GROUP_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMemberGroupTreeStore>(
export const UMB_MEMBER_GROUP_TREE_STORE_CONTEXT = new UmbContextToken<UmbMemberGroupTreeStore>(
'UmbMemberGroupTreeStore',
);

View File

@@ -0,0 +1,5 @@
import { EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
import type { UmbEntityTreeItemModel, UmbEntityTreeRootModel } from '@umbraco-cms/backoffice/tree';
export type UmbMemberGroupTreeItemModel = EntityTreeItemResponseModel & UmbEntityTreeItemModel;
export type UmbMemberGroupTreeRootModel = EntityTreeItemResponseModel & UmbEntityTreeRootModel;

View File

@@ -1,5 +1,5 @@
import { EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
import type { EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
export interface MemberGroupDetails extends EntityTreeItemResponseModel {
export interface UmbMemberGroupDetailModel extends EntityTreeItemResponseModel {
id: string; // TODO: Remove this when the backend is fixed
}

View File

@@ -0,0 +1 @@
export { UMB_MEMBER_GROUP_WORKSPACE_ALIAS } from './manifests.js';

View File

@@ -1,59 +1,23 @@
import { UmbSaveWorkspaceAction } from '@umbraco-cms/backoffice/workspace';
import { UMB_MEMBER_GROUP_ENTITY_TYPE } from '../entity.js';
import type {
ManifestWorkspace,
ManifestWorkspaceAction,
ManifestWorkspaceEditorView,
} from '@umbraco-cms/backoffice/extension-registry';
export const UMB_MEMBER_GROUP_WORKSPACE_ALIAS = 'Umb.Workspace.MemberGroup';
const workspace: ManifestWorkspace = {
type: 'workspace',
alias: 'Umb.Workspace.MemberGroup',
name: 'Member Group Workspace',
alias: UMB_MEMBER_GROUP_WORKSPACE_ALIAS,
name: 'MemberGroup Workspace',
loader: () => import('./member-group-workspace.element.js'),
meta: {
entityType: 'member-group',
entityType: UMB_MEMBER_GROUP_ENTITY_TYPE,
},
};
const workspaceViews: Array<ManifestWorkspaceEditorView> = [
{
type: 'workspaceEditorView',
alias: 'Umb.WorkspaceView.MemberGroup.Info',
name: 'Member Group Workspace Info View',
loader: () => import('./views/info/workspace-view-member-group-info.element.js'),
weight: 90,
meta: {
label: 'Info',
pathname: 'info',
icon: 'info',
},
conditions: [
{
alias: 'Umb.Condition.WorkspaceAlias',
match: workspace.alias,
},
],
},
];
const workspaceActions: Array<ManifestWorkspaceAction> = [
{
type: 'workspaceAction',
alias: 'Umb.WorkspaceAction.MemberGroup.Save',
name: 'Save Member Group Workspace Action',
api: UmbSaveWorkspaceAction,
meta: {
label: 'Save',
look: 'primary',
color: 'positive',
},
conditions: [
{
alias: 'Umb.Condition.WorkspaceAlias',
match: workspace.alias,
},
],
},
];
const workspaceViews: Array<ManifestWorkspaceEditorView> = [];
const workspaceActions: Array<ManifestWorkspaceAction> = [];
export const manifests = [workspace, ...workspaceViews, ...workspaceActions];

View File

@@ -1,70 +1,20 @@
import type { MemberGroupDetails } from '../types.js';
import { UMB_MEMBER_GROUP_WORKSPACE_CONTEXT } from './member-group-workspace.context.js';
import { UUIInputElement, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
/**
* @element umb-member-group-edit-workspace
* @description - Element for displaying a Member Group Workspace
*/
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, LitElement, customElement } from '@umbraco-cms/backoffice/external/lit';
@customElement('umb-member-group-workspace-editor')
export class UmbMemberGroupWorkspaceEditorElement extends UmbLitElement {
#workspaceContext?: typeof UMB_MEMBER_GROUP_WORKSPACE_CONTEXT.TYPE;
@state()
private _memberGroup?: MemberGroupDetails;
constructor() {
super();
this.consumeContext(UMB_MEMBER_GROUP_WORKSPACE_CONTEXT, (instance) => {
this.#workspaceContext = instance;
this.#observeMemberGroup();
});
}
#observeMemberGroup() {
if (!this.#workspaceContext) return;
this.observe(this.#workspaceContext.data, (data) => (this._memberGroup = data));
}
// TODO. find a way where we don't have to do this for all workspaces.
#handleInput(event: UUIInputEvent) {
if (event instanceof UUIInputEvent) {
const target = event.composedPath()[0] as UUIInputElement;
if (typeof target?.value === 'string') {
this.#workspaceContext?.setName(target.value);
}
}
}
export class UmbMemberGroupWorkspaceEditorElement extends LitElement {
render() {
return html`<umb-workspace-editor alias="Umb.Workspace.MemberGroup">
<div id="header" slot="header">
<uui-input id="name" .value=${this._memberGroup?.name} @input="${this.#handleInput}"> </uui-input>
</div>
</umb-workspace-editor> `;
return html` <umb-workspace-editor alias="Umb.Workspace.MemberGroup">MemberGroup Workspace</umb-workspace-editor> `;
}
static styles = [
UmbTextStyles,
css`
:host {
display: block;
width: 100%;
height: 100%;
}
#header {
margin: 0 var(--uui-size-layout-1);
flex: 1 1 auto;
}
#name {
width: 100%;
flex: 1 1 auto;
align-items: center;
}
`,
];
}

View File

@@ -1,73 +1,48 @@
import { UmbMemberGroupRepository } from '../repository/member-group.repository.js';
import type { MemberGroupDetails } from '../types.js';
import { UmbMemberGroupDetailRepository } from '../repository/index.js';
import type { UmbMemberGroupDetailModel } from '../types.js';
import { UMB_MEMBER_GROUP_ENTITY_TYPE } from '../entity.js';
import { UMB_MEMBER_GROUP_WORKSPACE_ALIAS } from './manifests.js';
import { UmbSaveableWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
type EntityType = MemberGroupDetails;
export class UmbMemberGroupWorkspaceContext
extends UmbWorkspaceContext<UmbMemberGroupRepository, EntityType>
implements UmbSaveableWorkspaceContextInterface<EntityType | undefined>
extends UmbWorkspaceContext<UmbMemberGroupDetailRepository, UmbMemberGroupDetailModel>
implements UmbSaveableWorkspaceContextInterface<UmbMemberGroupDetailModel | undefined>
{
#data = new UmbObjectState<EntityType | undefined>(undefined);
data = this.#data.asObservable();
name = this.#data.asObservablePart((data) => data?.name);
constructor(host: UmbControllerHostElement) {
super(host, 'Umb.Workspace.MemberGroup', new UmbMemberGroupRepository(host));
super(host, UMB_MEMBER_GROUP_WORKSPACE_ALIAS, new UmbMemberGroupDetailRepository(host));
}
getData() {
return this.#data.getValue();
getEntityType(): string {
return UMB_MEMBER_GROUP_ENTITY_TYPE;
}
getEntityId() {
return this.getData()?.id || '';
return '1234';
}
getEntityType() {
return 'member-group';
}
setName(name: string) {
this.#data.update({ name });
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
setPropertyValue(alias: string, value: string) {
// Not implemented for this context - member groups have no properties for editing
return;
}
async load(entityId: string) {
const { data } = await this.repository.requestById(entityId);
if (data) {
this.#data.next(data);
}
}
async create() {
const { data } = await this.repository.createScaffold();
if (!data) return;
this.setIsNew(true);
this.#data.next(data);
getData() {
return 'fake' as unknown as UmbMemberGroupDetailModel;
}
async save() {
if (!this.#data.value) return;
await this.repository.save(this.#data.value.id, this.#data.value);
this.setIsNew(true);
console.log('save');
}
async load(id: string) {
console.log('load', id);
}
public destroy(): void {
this.#data.destroy();
console.log('destroy');
}
}
export const UMB_MEMBER_GROUP_WORKSPACE_CONTEXT = new UmbContextToken<UmbSaveableWorkspaceContextInterface, UmbMemberGroupWorkspaceContext>(
export const UMB_MEMBER_GROUP_WORKSPACE_CONTEXT = new UmbContextToken<
UmbSaveableWorkspaceContextInterface,
UmbMemberGroupWorkspaceContext
>(
'UmbWorkspaceContext',
(context): context is UmbMemberGroupWorkspaceContext => context.getEntityType?.() === 'member-group'
(context): context is UmbMemberGroupWorkspaceContext => context.getEntityType?.() === UMB_MEMBER_GROUP_ENTITY_TYPE,
);

View File

@@ -1,14 +1,10 @@
import { UmbMemberGroupWorkspaceContext } from './member-group-workspace.context.js';
import { UmbMemberGroupWorkspaceEditorElement } from './member-group-workspace-editor.element.js';
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
import { UmbMemberGroupWorkspaceContext } from './member-group-workspace.context.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import type { UmbRoute } from '@umbraco-cms/backoffice/router';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
/**
* @element umb-member-group-workspace
* @description - Element for displaying a Member Group Workspace
*/
@customElement('umb-member-group-workspace')
export class UmbMemberGroupWorkspaceElement extends UmbLitElement {
#workspaceContext = new UmbMemberGroupWorkspaceContext(this);
@@ -27,7 +23,7 @@ export class UmbMemberGroupWorkspaceElement extends UmbLitElement {
];
render() {
return html`<umb-router-slot .routes=${this._routes}></umb-router-slot> `;
return html` <umb-router-slot .routes=${this._routes}></umb-router-slot> `;
}
static styles = [

View File

@@ -2,13 +2,13 @@ import './member-group-workspace.element.js';
import { Meta, Story } from '@storybook/web-components';
import { data } from '../../../../mocks/data/member-group.data.js';
import { data } from '../../../../mocks/data/member.data.js';
import type { UmbMemberGroupWorkspaceElement } from './member-group-workspace.element.js';
import { html } from '@umbraco-cms/backoffice/external/lit';
export default {
title: 'Workspaces/Member Group',
title: 'Workspaces/MemberGroup',
component: 'umb-member-group-workspace',
id: 'umb-member-group-workspace',
} as Meta;

View File

@@ -1,84 +0,0 @@
import { UMB_MEMBER_GROUP_WORKSPACE_CONTEXT } from '../../member-group-workspace.context.js';
import type { MemberGroupDetails } from '../../../types.js';
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
@customElement('umb-workspace-view-member-group-info')
export class UmbWorkspaceViewMemberGroupInfoElement extends UmbLitElement {
@state()
private _memberGroup?: MemberGroupDetails;
#workspaceContext?: typeof UMB_MEMBER_GROUP_WORKSPACE_CONTEXT.TYPE;
constructor() {
super();
this.consumeContext(UMB_MEMBER_GROUP_WORKSPACE_CONTEXT, (instance) => {
this.#workspaceContext = instance;
this.#observeMemberGroup();
});
}
#observeMemberGroup() {
if (!this.#workspaceContext) return;
this.observe(this.#workspaceContext.data, (memberGroup) => {
if (!memberGroup) return;
this._memberGroup = memberGroup;
});
}
private _renderGeneralInfo() {
return html`
<uui-box headline="General">
<umb-workspace-property-layout label="Key" orientation="vertical">
<div slot="editor">${this._memberGroup?.id}</div>
</umb-workspace-property-layout>
</uui-box>
`;
}
private _renderMemberGroupInfo() {
return html`
<uui-box headline="Member Group">
<umb-empty-state size="small">Member groups have no additional properties for editing.</umb-empty-state>
</uui-box>
`;
}
render() {
return html` ${this._renderMemberGroupInfo()}${this._renderGeneralInfo()} `;
}
static styles = [
UmbTextStyles,
css`
:host {
display: flex;
margin: var(--uui-size-layout-1);
gap: var(--uui-size-layout-1);
justify-content: space-between;
}
uui-box {
margin-bottom: var(--ui-size-layout-1);
}
uui-box:first-child {
flex: 1 1 75%;
}
uui-box:last-child {
min-width: 320px;
}
`,
];
}
export default UmbWorkspaceViewMemberGroupInfoElement;
declare global {
interface HTMLElementTagNameMap {
'umb-workspace-view-member-group-info': UmbWorkspaceViewMemberGroupInfoElement;
}
}

View File

@@ -1,26 +0,0 @@
import './workspace-view-member-group-info.element.js';
import { Meta, Story } from '@storybook/web-components';
import type { UmbWorkspaceViewMemberGroupInfoElement } from './workspace-view-member-group-info.element.js';
import { html } from '@umbraco-cms/backoffice/external/lit';
//import { data } from '../../../../../core/mocks/data/data-type.data.js';
//import { UmbDataTypeContext } from '../../data-type.context.js';
export default {
title: 'Workspaces/Data Type/Views/Info',
component: 'umb-workspace-view-member-group-info',
id: 'umb-workspace-view-member-group-info',
decorators: [
(story) => {
return html`TODO: make use of mocked workspace context??`;
/*html` <umb-context-provider key="umbDataTypeContext" .value=${new UmbDataTypeWorkspaceContext(data[0])}>
${story()}
</umb-context-provider>`,*/
},
],
} as Meta;
export const AAAOverview: Story<UmbWorkspaceViewMemberGroupInfoElement> = () =>
html` <umb-workspace-view-data-type-info></umb-workspace-view-data-type-info>`;
AAAOverview.storyName = 'Overview';

View File

@@ -1,3 +1,4 @@
import { UMB_MEMBER_MENU_ALIAS } from '../../menu.manifests.js';
import { UMB_MEMBER_TREE_ALIAS } from '../tree/index.js';
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
@@ -11,7 +12,7 @@ const menuItem: ManifestTypes = {
label: 'Members',
icon: 'icon-folder',
treeAlias: UMB_MEMBER_TREE_ALIAS,
menus: ['Umb.Menu.Members'],
menus: [UMB_MEMBER_MENU_ALIAS],
},
};

View File

@@ -1,8 +1,10 @@
import { ManifestMenu } from '@umbraco-cms/backoffice/extension-registry';
export const UMB_MEMBER_MENU_ALIAS = 'Umb.Menu.Member';
const menu: ManifestMenu = {
type: 'menu',
alias: 'Umb.Menu.Members',
alias: UMB_MEMBER_MENU_ALIAS,
name: 'Members Menu',
meta: {
label: 'Members',