scaffold members section

This commit is contained in:
Nathan Woulfe
2023-02-08 15:24:46 +10:00
parent 8374d0d04e
commit e4effeb3a7
18 changed files with 352 additions and 35 deletions

View File

@@ -137,6 +137,10 @@ export interface MemberGroupDetails extends EntityTreeItem {
key: string; // TODO: Remove this when the backend is fixed
}
export interface MemberDetails extends EntityTreeItem {
key: string; // TODO: Remove this when the backend is fixed
}
// Dictionary
export interface DictionaryDetails extends EntityTreeItem {
key: string; // TODO: Remove this when the backend is fixed

View File

@@ -27,6 +27,8 @@ import { UmbMemberTypeDetailStore } from './members/member-types/member-type.det
import { UmbMemberTypeTreeStore } from './members/member-types/member-type.tree.store';
import { UmbMemberGroupDetailStore } from './members/member-groups/member-group.detail.store';
import { UmbMemberGroupTreeStore } from './members/member-groups/member-group.tree.store';
import { UmbMemberDetailStore } from './members/members/member.detail.store';
import { UmbMemberTreeStore } from './members/members/member.tree.store';
import { UmbDictionaryDetailStore } from './translation/dictionary/dictionary.detail.store';
import { UmbDictionaryTreeStore } from './translation/dictionary/dictionary.tree.store';
import { UmbDocumentBlueprintDetailStore } from './documents/document-blueprints/document-blueprint.detail.store';
@@ -96,6 +98,8 @@ export class UmbBackofficeElement extends UmbLitElement {
new UmbUserGroupStore(this);
new UmbMemberGroupDetailStore(this);
new UmbMemberGroupTreeStore(this);
new UmbMemberDetailStore(this);
new UmbMemberTreeStore(this);
new UmbDictionaryDetailStore(this);
new UmbDictionaryTreeStore(this);
new UmbDocumentBlueprintDetailStore(this);

View File

@@ -1,7 +1,8 @@
import { Observable } from 'rxjs';
import { umbMemberGroupData } from '../../../core/mocks/data/member-group.data';
import type { MemberGroupDetails } from '@umbraco-cms/models';
import { UmbContextToken } from '@umbraco-cms/context-api';
import { ArrayState } from '@umbraco-cms/observable-api';
import { ArrayState, createObservablePart } from '@umbraco-cms/observable-api';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbEntityDetailStore, UmbStoreBase } from '@umbraco-cms/store';
@@ -33,28 +34,22 @@ export class UmbMemberGroupDetailStore extends UmbStoreBase implements UmbEntity
* @return {*} {(Observable<MemberGroupDetails>)}
* @memberof UmbMemberGroupDetailStore
*/
getByKey(key: string): Observable<MemberGroupDetails | undefined> {
getByKey(key: string): Observable<MemberGroupDetails> {
// tryExecuteAndNotify(this.host, MemberGroupResource.getMemberGroupByKey({ key })).then(({ data }) => {
// if (data) {
// this.#data.appendOne(data);
// }
// });
// return createObservablePart(
// this.#data,
// (groups) => groups.find((group) => group.key === key) as MemberGroupDetails
// );
// temp until Resource is updated
const group = umbMemberGroupData.getByKey(key);
if (group) {
this.#data.appendOne(group);
}
// TODO: use backend cli when available.
fetch(`/umbraco/management/api/v1/member-group/${key}`)
.then((res) => res.json())
.then((data) => {
this.#data.append(data);
console.log(data);
});
return this.#data.getObservablePart((memberGroups) =>
memberGroups.find((memberGroup) => memberGroup.key === key)
return createObservablePart(
this.#data,
(groups) => groups.find((group) => group.key === key) as MemberGroupDetails
);
}

View File

@@ -48,6 +48,7 @@ export class UmbMemberGroupTreeStore extends UmbStoreBase {
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);
console.log(this.#data);
}
});

View File

@@ -3,17 +3,14 @@ import { css, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '../../../../../core/modal';
import UmbTreeItemActionElement from '../../../../shared/components/tree/action/tree-item-action.element';
import {
UmbMemberGroupDetailStore,
UMB_MEMBER_GROUP_DETAIL_STORE_CONTEXT_TOKEN,
} from '../../member-group.detail.store';
import { UmbMemberGroupTreeStore, UMB_MEMBER_GROUP_TREE_STORE_CONTEXT_TOKEN } from '../../member-group.tree.store';
@customElement('umb-tree-action-member-group-delete')
export default class UmbTreeActionMemberGroupDeleteElement extends UmbTreeItemActionElement {
static styles = [UUITextStyles, css``];
private _modalService?: UmbModalService;
private _memberGroupDetailStore?: UmbMemberGroupDetailStore;
private _memberGroupTreeStore?: UmbMemberGroupTreeStore;
connectedCallback(): void {
super.connectedCallback();
@@ -22,8 +19,8 @@ export default class UmbTreeActionMemberGroupDeleteElement extends UmbTreeItemAc
this._modalService = modalService;
});
this.consumeContext(UMB_MEMBER_GROUP_DETAIL_STORE_CONTEXT_TOKEN, (memberGroupDetailStore) => {
this._memberGroupDetailStore = memberGroupDetailStore;
this.consumeContext(UMB_MEMBER_GROUP_TREE_STORE_CONTEXT_TOKEN, (memberGroupTreeStore) => {
this._memberGroupTreeStore = memberGroupTreeStore;
});
}
@@ -36,8 +33,8 @@ export default class UmbTreeActionMemberGroupDeleteElement extends UmbTreeItemAc
});
modalHandler?.onClose().then(({ confirmed }: any) => {
if (confirmed && this._treeContextMenuService && this._memberGroupDetailStore && this._activeTreeItem) {
this._memberGroupDetailStore?.trash([this._activeTreeItem.key]);
if (confirmed && this._treeContextMenuService && this._memberGroupTreeStore && this._activeTreeItem) {
this._memberGroupTreeStore?.delete([this._activeTreeItem.key]);
this._treeContextMenuService.close();
}
});

View File

@@ -1,5 +1,5 @@
import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models';
import { UMB_MEMBER_GROUP_TREE_STORE_CONTEXT_TOKEN } from '../member-group.tree.store';
import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models';
const treeAlias = 'Umb.Tree.MemberGroups';

View File

@@ -43,7 +43,6 @@ export class UmbWorkspaceViewMemberGroupInfoElement extends UmbLitElement {
// TODO: Figure out if this is the best way to consume the context or if it can be strongly typed with an UmbContextToken
this.consumeContext<UmbWorkspaceMemberGroupContext>('umbWorkspaceContext', (memberGroupContext) => {
this._workspaceContext = memberGroupContext;
console.log(memberGroupContext);
this._observeMemberGroup();
});
}
@@ -52,7 +51,6 @@ export class UmbWorkspaceViewMemberGroupInfoElement extends UmbLitElement {
if (!this._workspaceContext) return;
this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (memberGroup) => {
console.log(memberGroup);
if (!memberGroup) return;
// TODO: handle if model is not of the type wanted.

View File

@@ -0,0 +1,59 @@
import { Observable } from 'rxjs';
import type { MemberDetails, MemberGroupDetails } from '@umbraco-cms/models';
import { UmbContextToken } from '@umbraco-cms/context-api';
import { ArrayState, createObservablePart } from '@umbraco-cms/observable-api';
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbEntityDetailStore, UmbStoreBase } from '@umbraco-cms/store';
import { umbMemberData } from 'src/core/mocks/data/member.data';
export const UMB_MEMBER_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMemberDetailStore>('UmbMemberDetailStore');
/**
* @export
* @class UmbMemberDetailStore
* @extends {UmbStoreBase}
* @description - Detail Data Store for Members
*/
export class UmbMemberDetailStore extends UmbStoreBase implements UmbEntityDetailStore<MemberDetails> {
#data = new ArrayState<MemberDetails>([], x => x.key);
public groups = this.#data.asObservable();
constructor(private host: UmbControllerHostInterface) {
super(host, UMB_MEMBER_DETAIL_STORE_CONTEXT_TOKEN.toString());
}
getScaffold(entityType: string, parentKey: string | null) {
return {
} as MemberDetails;
}
/**
* @description - Request a Member by key. The Member is added to the store and is returned as an Observable.
* @param {string} key
* @return {*} {(Observable<MemberDetails>)}
* @memberof UmbMemberDetailStore
*/
getByKey(key: string): Observable<MemberGroupDetails> {
// tryExecuteAndNotify(this.host, MemberResource.getMemberByKey({ key })).then(({ data }) => {
// if (data) {}
// this.#data.appendOne(data);
// }
// });
// temp until Resource is updated
const member = umbMemberData.getByKey(key);
if (member) {
this.#data.appendOne(member);
}
return createObservablePart(
this.#data,
(members) => members.find((member) => member.key === key) as MemberDetails
);
}
async save(member: Array<MemberDetails>): Promise<void> {
return null as any;
}
}

View File

@@ -0,0 +1,102 @@
import { EntityTreeItem, MemberGroupResource } 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 { UmbControllerHostInterface } from '@umbraco-cms/controller';
export const UMB_MEMBER_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMemberTreeStore>('UmbMemberTreeStore');
/**
* @export
* @class UmbMemberTreeStore
* @extends {UmbStoreBase}
* @description - Tree Data Store for Members
*/
export class UmbMemberTreeStore extends UmbStoreBase {
// TODO: use the right type here:
#data = new ArrayState<EntityTreeItem>([], (x) => x.key);
constructor(host: UmbControllerHostInterface) {
super(host, UMB_MEMBER_TREE_STORE_CONTEXT_TOKEN.toString());
}
// TODO: How can we avoid having this in both stores?
/**
* @description - Delete a Member.
* @param {string[]} keys
* @memberof UmbMemberTreeStore
* @return {*} {Promise<void>}
*/
async delete(keys: string[]) {
// TODO: use backend cli when available.
await fetch('/umbraco/backoffice/members/delete', {
method: 'POST',
body: JSON.stringify(keys),
headers: {
'Content-Type': 'application/json',
},
});
this.#data.remove(keys);
}
async getTreeRoot() {
// TODO: use backend cli when available.
tryExecuteAndNotify(
this._host,
fetch('/umbraco/management/api/v1/tree/member/root')
.then((res) => res.json())
.then((data) => {
this.#data.append(data.items);
debugger;
})
);
console.log(this.#data);
// tryExecuteAndNotify(this._host, MembersResource.getTreeMembersRoot({})).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,
// MembersResource.getTreeMembersChildren({
// 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<string>) {
// if (keys?.length > 0) {
// tryExecuteAndNotify(
// this._host,
// MembersResource.getTreeMembersItem({
// 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 ?? '')));
}
}

View File

@@ -10,6 +10,7 @@ const sidebarMenuItem: ManifestSidebarMenuItem = {
label: 'Members',
icon: 'umb:folder',
sections: ['Umb.Section.Members'],
entityType: 'member',
},
};

View File

@@ -23,7 +23,8 @@ export class UmbMembersSidebarMenuItemElement extends UmbLitElement {
label="Members"
icon="umb:folder"
@show-children=${this._onShowChildren}
@hide-children=${this._onHideChildren}>
@hide-children=${this._onHideChildren}
has-children>
${this._renderTree ? html`<umb-tree alias="Umb.Tree.Members"></umb-tree>` : nothing}
</umb-tree-item> `;
}

View File

@@ -0,0 +1,54 @@
import { UUITextStyles } from '@umbraco-ui/uui-css';
import { css, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '../../../../../core/modal';
import UmbTreeItemActionElement from '../../../../shared/components/tree/action/tree-item-action.element';
import { UmbMemberTreeStore, UMB_MEMBER_TREE_STORE_CONTEXT_TOKEN } from '../../member.tree.store';
@customElement('umb-tree-action-member-delete')
export default class UmbTreeActionMemberDeleteElement extends UmbTreeItemActionElement {
static styles = [UUITextStyles, css``];
private _modalService?: UmbModalService;
private _memberTreeStore?: UmbMemberTreeStore;
connectedCallback(): void {
super.connectedCallback();
this.consumeContext(UMB_MODAL_SERVICE_CONTEXT_TOKEN, (modalService) => {
this._modalService = modalService;
});
this.consumeContext(UMB_MEMBER_TREE_STORE_CONTEXT_TOKEN, (memberTreeStore) => {
this._memberTreeStore = memberTreeStore;
});
}
private _handleLabelClick() {
const modalHandler = this._modalService?.confirm({
headline: `Delete ${this._activeTreeItem?.name ?? 'item'}`,
content: 'Are you sure you want to delete this item?',
color: 'danger',
confirmLabel: 'Delete',
});
modalHandler?.onClose().then(({ confirmed }: any) => {
if (confirmed && this._treeContextMenuService && this._memberTreeStore && this._activeTreeItem) {
this._memberTreeStore?.delete([this._activeTreeItem.key]);
this._treeContextMenuService.close();
}
});
}
render() {
return html`<uui-menu-item label=${this.treeAction?.meta.label ?? ''} @click-label="${this._handleLabelClick}">
<uui-icon slot="icon" name=${this.treeAction?.meta.icon ?? ''}></uui-icon>
</uui-menu-item>`;
}
}
declare global {
interface HTMLElementTagNameMap {
'umb-tree-action-member-delete': UmbTreeActionMemberDeleteElement;
}
}

View File

@@ -1,14 +1,29 @@
import { UMB_MEMBER_TREE_STORE_CONTEXT_TOKEN } from '../member.tree.store';
import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models';
const tree: ManifestTree = {
type: 'tree',
alias: 'Umb.Tree.Members',
name: 'Members Tree',
weight: 10,
meta: {
storeAlias: 'umbMemberTypesStore',
storeAlias: UMB_MEMBER_TREE_STORE_CONTEXT_TOKEN.toString(),
},
};
const treeItemActions: Array<ManifestTreeItemAction> = [];
const treeItemActions: Array<ManifestTreeItemAction> = [
{
type: 'treeItemAction',
alias: 'Umb.TreeItemAction.Member.Delete',
name: 'Member Tree Item Action Delete',
loader: () => import('./actions/action-member-delete.element'),
weight: 100,
meta: {
entityType: 'member',
label: 'Delete',
icon: 'delete',
},
},
];
export const manifests = [tree, ...treeItemActions];
export const manifests = [tree, ...treeItemActions];

View File

@@ -0,0 +1,18 @@
import './member-workspace.element';
import { Meta, Story } from '@storybook/web-components';
import { html } from 'lit-html';
import { data } from '../../../../core/mocks/data/member.data';
import type { UmbMemberWorkspaceElement } from './member-workspace.element';
export default {
title: 'Workspaces/Member',
component: 'umb-member-workspace',
id: 'umb-member-workspace',
} as Meta;
export const AAAOverview: Story<UmbMemberWorkspaceElement> = () =>
html` <umb-member-workspace id="${data[0].key}"></umb-member-workspace>`;
AAAOverview.storyName = 'Overview';

View File

@@ -18,6 +18,7 @@ import { handlers as mediaHandlers } from './domains/media.handlers';
import { handlers as dictionaryHandlers } from './domains/dictionary.handlers';
import { handlers as mediaTypeHandlers } from './domains/media-type.handlers';
import { handlers as memberGroupHandlers } from './domains/member-group.handlers';
import { handlers as memberHandlers } from './domains/member.handlers';
import { handlers as memberTypeHandlers } from './domains/member-type.handlers';
import { handlers as templateHandlers } from './domains/template.handlers';
import { handlers as languageHandlers } from './domains/language.handlers';
@@ -39,6 +40,7 @@ const handlers = [
...userGroupsHandlers,
...mediaTypeHandlers,
...memberGroupHandlers,
...memberHandlers,
...memberTypeHandlers,
...examineManagementHandlers,
...modelsBuilderHandlers,

View File

@@ -1,11 +1,11 @@
import type { MemberGroupDetails } from '@umbraco-cms/models';
import { EntityTreeItem, PagedEntityTreeItem } from '@umbraco-cms/backend-api';
import { UmbEntityData } from './entity.data';
import { createEntityTreeItem } from './utils';
import type { MemberGroupDetails } from '@umbraco-cms/models';
import { EntityTreeItem, PagedEntityTreeItem } from '@umbraco-cms/backend-api';
export const data: Array<MemberGroupDetails> = [
{
name: 'Member Group 1',
name: 'Member Group AAA',
type: 'member-group',
icon: 'umb:document',
hasChildren: false,

View File

@@ -0,0 +1,47 @@
import { UmbEntityData } from './entity.data';
import { createEntityTreeItem } from './utils';
import type { MemberDetails } from '@umbraco-cms/models';
import { EntityTreeItem, PagedEntityTreeItem } from '@umbraco-cms/backend-api';
export const data: Array<MemberDetails> = [
{
name: 'Member AAA',
type: 'member',
icon: 'umb:user',
hasChildren: false,
key: 'aaa08ccd-4179-464c-b634-6969149dd9f9',
isContainer: false,
parentKey: null,
},
];
// 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 UmbMemberData extends UmbEntityData<MemberDetails> {
constructor() {
super(data);
}
getTreeRoot(): PagedEntityTreeItem {
const items = this.data.filter((item) => item.parentKey === null);
const treeItems = items.map((item) => createEntityTreeItem(item));
const total = items.length;
return { items: treeItems, total };
}
getTreeItemChildren(key: string): PagedEntityTreeItem {
const items = this.data.filter((item) => item.parentKey === key);
const treeItems = items.map((item) => createEntityTreeItem(item));
const total = items.length;
return { items: treeItems, total };
}
getTreeItem(keys: Array<string>): Array<EntityTreeItem> {
const items = this.data.filter((item) => keys.includes(item.key ?? ''));
return items.map((item) => createEntityTreeItem(item));
}
}
export const umbMemberData = new UmbMemberData();

View File

@@ -0,0 +1,19 @@
import { rest } from 'msw';
import { umbMemberData } from '../data/member.data';
// TODO: add schema
export const handlers = [
rest.get('/umbraco/management/api/v1/tree/member/root', (req, res, ctx) => {
const response = umbMemberData.getTreeRoot();
return res(ctx.status(200), ctx.json(response));
}),
rest.get('/umbraco/management/api/v1/tree/member/item', (req, res, ctx) => {
const keys = req.url.searchParams.getAll('key');
if (!keys) return;
const items = umbMemberData.getTreeItem(keys);
return res(ctx.status(200), ctx.json(items));
}),
];