From b6bf4d18fd277e8e966f892ac5cd5a50ec10d000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 13:59:42 +0100 Subject: [PATCH 01/35] document store split --- .../src/backoffice/backoffice.element.ts | 6 +- .../documents/document.detail.store.ts | 83 +++++++++++ .../documents/documents/document.store.ts | 141 ------------------ .../documents/document.tree.store.ts | 91 +++++++++++ .../actions/action-document-delete.element.ts | 7 +- .../documents/documents/tree/manifests.ts | 2 +- .../workspace/document-workspace.context.ts | 4 +- .../shared/collection/collection.context.ts | 4 +- .../input-document-picker.element.ts | 7 +- .../components/tree/tree-item.element.ts | 6 +- .../shared/components/tree/tree.element.ts | 6 +- .../unique-array-behavior-subject.ts | 1 + .../src/core/stores/store-base.ts | 11 ++ .../src/core/stores/store.ts | 71 +-------- 14 files changed, 216 insertions(+), 224 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.store.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts create mode 100644 src/Umbraco.Web.UI.Client/src/core/stores/store-base.ts 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 2712f528bc..241cab9d39 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts @@ -18,7 +18,7 @@ import { } from './documents/document-types/document-type.store'; import { UmbMediaTypeStore, UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN } from './media/media-types/media-type.store'; import { UmbMemberTypeStore, UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN } from './members/member-types/member-type.store'; -import { UmbDocumentStore, UMB_DOCUMENT_STORE_CONTEXT_TOKEN } from './documents/documents/document.store'; +import { UmbDocumentStore, UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN } from './documents/documents/document.detail.store'; import { UmbMediaStore, UMB_MEDIA_STORE_CONTEXT_TOKEN } from './media/media/media.store'; import { UmbMemberGroupStore, UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN } from './members/member-groups/member-group.store'; import { UmbDictionaryStore, UMB_DICTIONARY_STORE_CONTEXT_TOKEN } from './translation/dictionary/dictionary.store'; @@ -67,7 +67,9 @@ export class UmbBackofficeElement extends UmbLitElement { // TODO: find a way this is possible outside this element. It needs to be possible to register stores in extensions this.provideContext(UMB_CURRENT_USER_STORE_CONTEXT_TOKEN, new UmbCurrentUserStore()); - this.provideContext(UMB_DOCUMENT_STORE_CONTEXT_TOKEN, new UmbDocumentStore(this)); + + new UmbDocumentStore(this); + this.provideContext(UMB_MEDIA_STORE_CONTEXT_TOKEN, new UmbMediaStore(this)); this.provideContext(UMB_DATA_TYPE_STORE_CONTEXT_TOKEN, new UmbDataTypeStore(this)); this.provideContext(UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN, new UmbDocumentTypeStore(this)); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts new file mode 100644 index 0000000000..452c7bee12 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts @@ -0,0 +1,83 @@ +import { map, Observable } from 'rxjs'; +import type { DocumentDetails } from '@umbraco-cms/models'; +import { DocumentResource, DocumentTreeItem, FolderTreeItem } from '@umbraco-cms/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; + + +export const UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDocumentDetailStore'); + + +/** + * @export + * @class UmbDocumentStore + * @extends {UmbStoreBase} + * @description - Data Store for Documents + */ +export class UmbDocumentDetailStore extends UmbStoreBase { + + + private _data = new UniqueArrayBehaviorSubject([], (a, b) => a.key === b.key); + + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN.toString()); + } + + getByKey(key: string): Observable { + // TODO: use backend cli when available. + fetch(`/umbraco/management/api/v1/document/details/${key}`) + .then((res) => res.json()) + .then((data) => { + this._data.append(data); + }); + + return createObservablePart(this._data, (documents) => + documents.find((document) => document.key === key) + ); + } + + // TODO: make sure UI somehow can follow the status of this action. + save(data: DocumentDetails[]): Promise { + // fetch from server and update store + // TODO: use Fetcher API. + let body: string; + + try { + body = JSON.stringify(data); + } catch (error) { + console.error(error); + return Promise.reject(); + } + + // TODO: use backend cli when available. + return fetch('/umbraco/management/api/v1/document/save', { + method: 'POST', + body: body, + headers: { + 'Content-Type': 'application/json', + }, + }) + .then((res) => res.json()) + .then((data: Array) => { + this._data.append(data); + }); + } + + // TODO: how do we handle trashed items? + async trash(keys: Array) { + // TODO: use backend cli when available. + const res = await fetch('/umbraco/management/api/v1/document/trash', { + method: 'POST', + body: JSON.stringify(keys), + headers: { + 'Content-Type': 'application/json', + }, + }); + const data = await res.json(); + this._data.next(data); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.store.ts deleted file mode 100644 index 57fdab43b5..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.store.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { map, Observable } from 'rxjs'; -import { UmbNodeStoreBase } from '../../../core/stores/store'; -import type { DocumentDetails } from '@umbraco-cms/models'; -import { DocumentResource, DocumentTreeItem, FolderTreeItem } from '@umbraco-cms/backend-api'; -import { tryExecuteAndNotify } from '@umbraco-cms/resources'; -import { UmbContextToken } from '@umbraco-cms/context-api'; -import { createObservablePart } from '@umbraco-cms/observable-api'; - -export const isDocumentDetails = (document: DocumentDetails | DocumentTreeItem): document is DocumentDetails => { - return (document as DocumentDetails).data !== undefined; -}; - -export type UmbDocumentStoreItemType = DocumentDetails | DocumentTreeItem; - -// TODO: research how we write names of global consts. -export const STORE_ALIAS = 'UmbDocumentStore'; - -/** - * @export - * @class UmbDocumentStore - * @extends {UmbDocumentStoreBase} - * @description - Data Store for Documents - */ -export class UmbDocumentStore extends UmbNodeStoreBase { - public readonly storeAlias = STORE_ALIAS; - - getByKey(key: string): Observable { - // TODO: use backend cli when available. - fetch(`/umbraco/management/api/v1/document/details/${key}`) - .then((res) => res.json()) - .then((data) => { - this.updateItems(data); - }); - - /* - return this.items.pipe( - map( - (documents) => - (documents.find((document) => document.key === key && isDocumentDetails(document)) as DocumentDetails) || null - ) - ); - */ - - return createObservablePart(this.items, (documents) => - (documents.find((document) => document.key === key && isDocumentDetails(document)) as DocumentDetails) - ); - } - - // TODO: make sure UI somehow can follow the status of this action. - save(data: DocumentDetails[]): Promise { - // fetch from server and update store - // TODO: use Fetcher API. - let body: string; - - try { - body = JSON.stringify(data); - } catch (error) { - console.error(error); - return Promise.reject(); - } - - // TODO: use backend cli when available. - return fetch('/umbraco/management/api/v1/document/save', { - method: 'POST', - body: body, - headers: { - 'Content-Type': 'application/json', - }, - }) - .then((res) => res.json()) - .then((data: Array) => { - this.updateItems(data); - }); - } - - // TODO: how do we handle trashed items? - async trash(keys: Array) { - // TODO: use backend cli when available. - const res = await fetch('/umbraco/management/api/v1/document/trash', { - method: 'POST', - body: JSON.stringify(keys), - headers: { - 'Content-Type': 'application/json', - }, - }); - const data = await res.json(); - this.updateItems(data); - } - - getTreeRoot(): Observable> { - tryExecuteAndNotify(this.host, DocumentResource.getTreeDocumentRoot({})).then(({ data }) => { - if (data) { - this.updateItems(data.items); - } - }); - - // TODO: how do we handle trashed items? - // TODO: remove ignore when we know how to handle trashed items. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return this.items.pipe(map((items) => items.filter((item) => item.parentKey === null && !item.isTrashed))); - } - - getTreeItemChildren(key: string): Observable> { - tryExecuteAndNotify( - this.host, - DocumentResource.getTreeDocumentChildren({ - parentKey: key, - }) - ).then(({ data }) => { - if (data) { - this.updateItems(data.items); - } - }); - - // TODO: how do we handle trashed items? - // TODO: remove ignore when we know how to handle trashed items. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return this.items.pipe(map((items) => items.filter((item) => item.parentKey === key && !item.isTrashed))); - } - - getTreeItems(keys: Array): Observable> { - if (keys?.length > 0) { - tryExecuteAndNotify( - this.host, - DocumentResource.getTreeDocumentItem({ - key: keys, - }) - ).then(({ data }) => { - if (data) { - this.updateItems(data); - } - }); - } - - return this.items.pipe(map((items) => items.filter((item) => keys.includes(item.key ?? '')))); - } -} - -export const UMB_DOCUMENT_STORE_CONTEXT_TOKEN = new UmbContextToken(STORE_ALIAS); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts new file mode 100644 index 0000000000..3fb2f4d645 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts @@ -0,0 +1,91 @@ +import type { Observable } from 'rxjs'; +import { DocumentResource, DocumentTreeItem } from '@umbraco-cms/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; + + +export const UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDocumentDetailStore'); + + +/** + * @export + * @class UmbDocumentStore + * @extends {UmbStoreBase} + * @description - Data Store for Documents + */ +export class UmbDocumentTreeStore extends UmbStoreBase { + + + private _data = new UniqueArrayBehaviorSubject([], (a, b) => a.key === b.key); + + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN.toString()); + } + + // TODO: how do we handle trashed items? + async trash(keys: Array) { + // TODO: use backend cli when available. + const res = await fetch('/umbraco/management/api/v1/document/trash', { + method: 'POST', + body: JSON.stringify(keys), + headers: { + 'Content-Type': 'application/json', + }, + }); + const data = await res.json(); + this._data.next(data); + } + + getTreeRoot(): Observable> { + tryExecuteAndNotify(this._host, DocumentResource.getTreeDocumentRoot({})).then(({ data }) => { + if (data) { + this._data.append(data.items); + } + }); + + // TODO: how do we handle if an item has been removed during this session(like in another tab or by another user)? + // TODO: how do we handle trashed items? + // TODO: remove ignore when we know how to handle trashed items. + return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === null && !item.isTrashed)); + } + + getTreeItemChildren(key: string): Observable> { + tryExecuteAndNotify( + this._host, + DocumentResource.getTreeDocumentChildren({ + parentKey: key, + }) + ).then(({ data }) => { + if (data) { + this._data.append(data.items); + } + }); + + // TODO: how do we handle if an item has been removed during this session(like in another tab or by another user)? + // TODO: how do we handle trashed items? + // TODO: remove ignore when we know how to handle trashed items. + return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === key && !item.isTrashed)); + } + + getTreeItems(keys: Array): Observable> { + if (keys?.length > 0) { + tryExecuteAndNotify( + this._host, + DocumentResource.getTreeDocumentItem({ + 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 createObservablePart(this._data, (items) => items.filter((item) => keys.includes(item.key ?? ''))); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/tree/actions/action-document-delete.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/tree/actions/action-document-delete.element.ts index 9c635f9d6e..34a5180f84 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/tree/actions/action-document-delete.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/tree/actions/action-document-delete.element.ts @@ -2,7 +2,8 @@ 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 { UmbDocumentStore, UMB_DOCUMENT_STORE_CONTEXT_TOKEN } from '../../document.store'; +import type { UmbDocumentDetailStore } from '../../document.detail.store'; +import { UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN } from '../../document.detail.store'; import UmbTreeItemActionElement from '../../../../shared/components/tree/action/tree-item-action.element'; @customElement('umb-tree-action-document-delete') @@ -10,7 +11,7 @@ export default class UmbTreeActionDocumentDeleteElement extends UmbTreeItemActio static styles = [UUITextStyles, css``]; private _modalService?: UmbModalService; - private _documentStore?: UmbDocumentStore; + private _documentStore?: UmbDocumentDetailStore; connectedCallback(): void { super.connectedCallback(); @@ -19,7 +20,7 @@ export default class UmbTreeActionDocumentDeleteElement extends UmbTreeItemActio this._modalService = modalService; }); - this.consumeContext(UMB_DOCUMENT_STORE_CONTEXT_TOKEN, (documentStore) => { + this.consumeContext(UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN, (documentStore) => { this._documentStore = documentStore; }); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/tree/manifests.ts index dd544de291..09a36620ae 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/tree/manifests.ts @@ -1,4 +1,4 @@ -import { STORE_ALIAS } from '../document.store'; +import { STORE_ALIAS } from '../document.detail.store'; import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models'; const treeAlias = 'Umb.Tree.Documents'; 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 e8256d17cf..da50760cdf 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 @@ -1,6 +1,6 @@ import { UmbWorkspaceContentContext } from '../../../shared/components/workspace/workspace-content/workspace-content.context'; -import { STORE_ALIAS as DOCUMENT_STORE_ALIAS } from '../../../documents/documents/document.store'; -import type { UmbDocumentStore, UmbDocumentStoreItemType } from '../../../documents/documents/document.store'; +import { STORE_ALIAS as DOCUMENT_STORE_ALIAS } from '../document.detail.store'; +import type { UmbDocumentStore, UmbDocumentStoreItemType } from '../document.detail.store'; import type { UmbControllerHostInterface } from '@umbraco-cms/controller'; import type { DocumentDetails } from '@umbraco-cms/models'; import { appendToFrozenArray } from '@umbraco-cms/observable-api'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts index 8a23c626b1..3bdcfe9ebc 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts @@ -1,11 +1,11 @@ import { ContentTreeItem } from '@umbraco-cms/backend-api'; -import { UmbTreeDataStore } from '@umbraco-cms/stores/store'; +import { UmbTreeStore } from '@umbraco-cms/stores/store'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbContextToken, UmbContextConsumerController } from '@umbraco-cms/context-api'; import { UniqueBehaviorSubject, UmbObserverController } from '@umbraco-cms/observable-api'; export class UmbCollectionContext< DataType extends ContentTreeItem, - StoreType extends UmbTreeDataStore = UmbTreeDataStore + StoreType extends UmbTreeStore = UmbTreeStore > { private _host: UmbControllerHostInterface; private _entityKey: string | null; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-document-picker/input-document-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-document-picker/input-document-picker.element.ts index 31432bdac6..5c1e5ddb40 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-document-picker/input-document-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-document-picker/input-document-picker.element.ts @@ -7,7 +7,8 @@ import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from 'src/core/modal import type { FolderTreeItem } from '@umbraco-cms/backend-api'; import { UmbLitElement } from '@umbraco-cms/element'; import type { UmbObserverController } from '@umbraco-cms/observable-api'; -import { UmbDocumentStore, UMB_DOCUMENT_STORE_CONTEXT_TOKEN } from 'src/backoffice/documents/documents/document.store'; +import type { UmbDocumentDetailStore } from 'src/backoffice/documents/documents/document.detail.store'; +import { UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN } from 'src/backoffice/documents/documents/document.detail.store'; @customElement('umb-input-document-picker') export class UmbInputDocumentPickerElement extends FormControlMixin(UmbLitElement) { @@ -77,7 +78,7 @@ export class UmbInputDocumentPickerElement extends FormControlMixin(UmbLitElemen private _items?: Array; private _modalService?: UmbModalService; - private _documentStore?: UmbDocumentStore; + private _documentStore?: UmbDocumentDetailStore; private _pickedItemsObserver?: UmbObserverController; constructor() { @@ -94,7 +95,7 @@ export class UmbInputDocumentPickerElement extends FormControlMixin(UmbLitElemen () => !!this.max && this._selectedKeys.length > this.max ); - this.consumeContext(UMB_DOCUMENT_STORE_CONTEXT_TOKEN, (instance) => { + this.consumeContext(UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN, (instance) => { this._documentStore = instance; this._observePickedDocuments(); }); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree-item.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree-item.element.ts index 83d7affd20..e2080afd81 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree-item.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree-item.element.ts @@ -11,7 +11,7 @@ import { UMB_TREE_CONTEXT_MENU_SERVICE_CONTEXT_TOKEN, } from './context-menu/tree-context-menu.service'; import type { Entity } from '@umbraco-cms/models'; -import { UmbTreeDataStore } from '@umbraco-cms/stores/store'; +import { UmbTreeStore } from '@umbraco-cms/stores/store'; import { UmbLitElement } from '@umbraco-cms/element'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; @@ -68,7 +68,7 @@ export class UmbTreeItem extends UmbLitElement { private _hasActions = false; private _treeContext?: UmbTreeContextBase; - private _store?: UmbTreeDataStore; + private _store?: UmbTreeStore; private _sectionContext?: UmbSectionContext; private _treeContextMenuService?: UmbTreeContextMenuService; @@ -81,7 +81,7 @@ export class UmbTreeItem extends UmbLitElement { this._observeIsSelected(); }); - this.consumeContext('umbStore', (store: UmbTreeDataStore) => { + this.consumeContext('umbStore', (store: UmbTreeStore) => { this._store = store; }); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree.element.ts index 9af9c01147..40b3bb7aff 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/tree/tree.element.ts @@ -5,7 +5,7 @@ import { repeat } from 'lit-html/directives/repeat.js'; import { UmbTreeContextBase } from './tree.context'; import type { Entity, ManifestTree } from '@umbraco-cms/models'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; -import { UmbTreeDataStore } from '@umbraco-cms/stores/store'; +import { UmbTreeStore } from '@umbraco-cms/stores/store'; import { UmbLitElement } from '@umbraco-cms/element'; import './tree-item.element'; @@ -66,7 +66,7 @@ export class UmbTreeElement extends UmbLitElement { private _loading = true; private _treeContext?: UmbTreeContextBase; - private _store?: UmbTreeDataStore; + private _store?: UmbTreeStore; connectedCallback(): void { super.connectedCallback(); @@ -108,7 +108,7 @@ export class UmbTreeElement extends UmbLitElement { if (!this._tree?.meta.storeAlias) return; - this.consumeContext(this._tree.meta.storeAlias, (store: UmbTreeDataStore) => { + this.consumeContext(this._tree.meta.storeAlias, (store: UmbTreeStore) => { this._store = store; this.provideContext('umbStore', store); }); diff --git a/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-array-behavior-subject.ts b/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-array-behavior-subject.ts index 2ab466b647..a34aa895c8 100644 --- a/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-array-behavior-subject.ts +++ b/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-array-behavior-subject.ts @@ -49,6 +49,7 @@ export class UniqueArrayBehaviorSubject extends UniqueBehaviorSubject { * ]); */ append(entries: T[]) { + // TODO: stop calling appendOne for each but make sure to handle this in one. entries.forEach(x => this.appendOne(x)) } } diff --git a/src/Umbraco.Web.UI.Client/src/core/stores/store-base.ts b/src/Umbraco.Web.UI.Client/src/core/stores/store-base.ts new file mode 100644 index 0000000000..97430508ce --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/stores/store-base.ts @@ -0,0 +1,11 @@ +import { UmbContextProviderController } from "../context-api/provide/context-provider.controller"; +import { UmbControllerHostInterface } from "../controller/controller-host.mixin"; + +export class UmbStoreBase { + + + constructor (protected _host: UmbControllerHostInterface, public readonly storeAlias: string) { + new UmbContextProviderController(_host, storeAlias, this); + } + +} diff --git a/src/Umbraco.Web.UI.Client/src/core/stores/store.ts b/src/Umbraco.Web.UI.Client/src/core/stores/store.ts index 3720a2f01a..c37274b158 100644 --- a/src/Umbraco.Web.UI.Client/src/core/stores/store.ts +++ b/src/Umbraco.Web.UI.Client/src/core/stores/store.ts @@ -1,6 +1,4 @@ import type { Observable } from 'rxjs'; -import { UmbControllerHostInterface } from '../controller/controller-host.mixin'; -import { UniqueBehaviorSubject } from '../observable-api/unique-behavior-subject'; export interface UmbDataStoreIdentifiers { key?: string; @@ -9,80 +7,25 @@ export interface UmbDataStoreIdentifiers { export interface UmbDataStore { readonly storeAlias: string; + + // TODO: is items the right name? readonly items: Observable>; updateItems(items: Array): void; } -export interface UmbTreeDataStore extends UmbDataStore { +export interface UmbTreeStore extends UmbDataStore { getTreeRoot(): Observable>; getTreeItemChildren(key: string): Observable>; } -/** - * @export - * @class UmbDataStoreBase - * @implements {UmbDataStore} - * @template T - * @description - Base class for Data Stores - */ -export abstract class UmbDataStoreBase implements UmbDataStore { - public abstract readonly storeAlias: string; - - protected _items = new UniqueBehaviorSubject(>[]); - public readonly items = this._items.asObservable(); - - protected host: UmbControllerHostInterface; - - constructor(host: UmbControllerHostInterface) { - this.host = host; - } - - /** - * @description - Delete items from the store. - * @param {Array} keys - * @memberof UmbDataStoreBase - */ - public deleteItems(keys: Array): void { - const remainingItems = this._items.getValue().filter((item) => item.key && keys.includes(item.key) === false); - this._items.next(remainingItems); - } - - /** - * @description - Update the store with new items. Existing items are updated, new items are added, old are kept. Items are matched by the compareKey. - * @param {Array} items - * @param {keyof T} [compareKey='key'] - * @memberof UmbDataStoreBase - */ - public updateItems(items: Array, compareKey: keyof T = 'key'): void { - const newData = [...this._items.getValue()]; - items.forEach((newItem) => { - const storedItemIndex = newData.findIndex((item) => item[compareKey] === newItem[compareKey]); - if (storedItemIndex !== -1) { - newData[storedItemIndex] = newItem; - } else { - newData.push(newItem); - } - }); - - this._items.next(newData); - } -} - -/** - * @export - * @class UmbNodeStoreBase - * @implements {UmbDataStore} - * @template T - * @description - Base class for Data Stores - */ -export abstract class UmbNodeStoreBase extends UmbDataStoreBase { +export interface UmbContentStore extends UmbDataStore { /** * @description - Request data by key. The data is added to the store and is returned as an Observable. * @param {string} key - * @return {*} {(Observable)} + * @return {*} {(Observable)} * @memberof UmbDataStoreBase */ - abstract getByKey(key: string): Observable; + getByKey(key: string): Observable; /** * @description - Save data. @@ -90,5 +33,5 @@ export abstract class UmbNodeStoreBase extend * @return {*} {(Promise)} * @memberof UmbNodeStoreBase */ - abstract save(data: T[]): Promise; + save(data: T[]): Promise; } From 8cba56a19c81071241903564e0bc0d6e9cb10859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 14:42:24 +0100 Subject: [PATCH 02/35] more refactor on document store --- .../documents/document.detail.store.ts | 7 ++-- .../documents/document.tree.store.ts | 8 ++--- .../workspace/document-workspace.context.ts | 9 +++-- .../workspace/data-type-workspace.context.ts | 4 +-- .../workspace-content.context.ts | 13 ++++--- .../append-to-frozen-array.method.ts | 32 +++++++++++++++++ .../unique-array-behavior-subject.ts | 35 ++++++++++++++++--- .../observable-api/unique-behavior-subject.ts | 30 ---------------- 8 files changed, 81 insertions(+), 57 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/core/observable-api/append-to-frozen-array.method.ts diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts index 452c7bee12..484f6cc005 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts @@ -1,11 +1,10 @@ -import { map, Observable } from 'rxjs'; +import { Observable } from 'rxjs'; import type { DocumentDetails } from '@umbraco-cms/models'; -import { DocumentResource, DocumentTreeItem, FolderTreeItem } from '@umbraco-cms/backend-api'; -import { tryExecuteAndNotify } from '@umbraco-cms/resources'; import { UmbContextToken } from '@umbraco-cms/context-api'; import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { UmbContentStore } from '@umbraco-cms/stores/store'; export const UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDocumentDetailStore'); @@ -17,7 +16,7 @@ export const UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken} * @description - Data Store for Documents */ -export class UmbDocumentDetailStore extends UmbStoreBase { +export class UmbDocumentDetailStore extends UmbStoreBase implements UmbContentStore { private _data = new UniqueArrayBehaviorSubject([], (a, b) => a.key === b.key); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts index 3fb2f4d645..0c2e2c5a91 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts @@ -19,7 +19,7 @@ export const UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken([], (a, b) => a.key === b.key); + private _data = new UniqueArrayBehaviorSubject([], (x) => x.key); constructor(host: UmbControllerHostInterface) { @@ -37,17 +37,17 @@ export class UmbDocumentTreeStore extends UmbStoreBase { }, }); const data = await res.json(); - this._data.next(data); + this._data.append(data); } getTreeRoot(): Observable> { tryExecuteAndNotify(this._host, DocumentResource.getTreeDocumentRoot({})).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: how do we handle if an item has been removed during this session(like in another tab or by another user)? // TODO: how do we handle trashed items? // TODO: remove ignore when we know how to handle trashed items. return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === null && !item.isTrashed)); @@ -61,11 +61,11 @@ export class UmbDocumentTreeStore extends UmbStoreBase { }) ).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: how do we handle if an item has been removed during this session(like in another tab or by another user)? // TODO: how do we handle trashed items? // TODO: remove ignore when we know how to handle trashed items. return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === key && !item.isTrashed)); 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 da50760cdf..368d118371 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 @@ -1,6 +1,6 @@ import { UmbWorkspaceContentContext } from '../../../shared/components/workspace/workspace-content/workspace-content.context'; -import { STORE_ALIAS as DOCUMENT_STORE_ALIAS } from '../document.detail.store'; -import type { UmbDocumentStore, UmbDocumentStoreItemType } from '../document.detail.store'; +import { DOCUMENT_DETAIl_STORE_ALIAS } from '../document.detail.store'; +import type { UmbDocumentDetailStore, UmbDocumentStoreItemType } from '../document.detail.store'; import type { UmbControllerHostInterface } from '@umbraco-cms/controller'; import type { DocumentDetails } from '@umbraco-cms/models'; import { appendToFrozenArray } from '@umbraco-cms/observable-api'; @@ -36,16 +36,15 @@ const DefaultDocumentData = { export class UmbWorkspaceDocumentContext extends UmbWorkspaceContentContext { constructor(host: UmbControllerHostInterface) { - super(host, DefaultDocumentData, DOCUMENT_STORE_ALIAS, 'document'); + super(host, DefaultDocumentData, DOCUMENT_DETAIl_STORE_ALIAS, 'document'); } public setPropertyValue(alias: string, value: unknown) { // TODO: make sure to check that we have a details model? otherwise fail? 8This can be relevant if we use the same context for tree actions? - //if(isDocumentDetails(data)) { ... } const entry = {alias: alias, value: value}; - const newDataSet = appendToFrozenArray((this._data.getValue() as DocumentDetails).data, entry, x => x.alias === alias); + const newDataSet = appendToFrozenArray((this._data.getValue() as DocumentDetails).data, entry, (x: any) => x.alias); this.update({data: newDataSet}); } 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 678d491b39..3d3a0eed5f 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 @@ -4,7 +4,7 @@ import { UmbDataTypeStoreItemType, UMB_DATA_TYPE_STORE_CONTEXT_TOKEN, } from 'src/backoffice/settings/data-types/data-type.store'; -import type { DataTypeDetails } from '@umbraco-cms/models'; +import type { DataTypeDetails, DataTypePropertyData } from '@umbraco-cms/models'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { appendToFrozenArray } from '@umbraco-cms/observable-api'; @@ -35,7 +35,7 @@ export class UmbWorkspaceDataTypeContext extends UmbWorkspaceContentContext< const newDataSet = appendToFrozenArray( (this._data.getValue() as DataTypeDetails).data, entry, - (x) => x.alias === alias + (x: DataTypePropertyData) => x.alias ); this.update({ data: newDataSet }); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts index f700645f0a..525f3b9c25 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts @@ -1,18 +1,17 @@ import { v4 as uuidv4 } from 'uuid'; import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '../../../../../core/notification'; import { UmbNotificationDefaultData } from '../../../../../core/notification/layouts/default'; -import { UmbNodeStoreBase } from '@umbraco-cms/stores/store'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbContextConsumerController, UmbContextProviderController } from '@umbraco-cms/context-api'; -import { EntityTreeItem } from '@umbraco-cms/backend-api'; -import { UniqueBehaviorSubject, UmbObserverController } from '@umbraco-cms/observable-api'; -import { createObservablePart } from '@umbraco-cms/observable-api'; +import { UniqueBehaviorSubject, UmbObserverController, createObservablePart } from '@umbraco-cms/observable-api'; +import { UmbContentStore } from '@umbraco-cms/stores/store'; +import type { ContentDetails } from '@umbraco-cms/models'; // TODO: Consider if its right to have this many class-inheritance of WorkspaceContext // TODO: Could we extract this code into a 'Manager' of its own, which will be instantiated by the concrete Workspace Context. This will be more transparent and 'reuseable' export abstract class UmbWorkspaceContentContext< - ContentTypeType extends EntityTreeItem = EntityTreeItem, - StoreType extends UmbNodeStoreBase = UmbNodeStoreBase + ContentTypeType extends ContentDetails, + StoreType extends UmbContentStore > { protected _host: UmbControllerHostInterface; @@ -104,7 +103,7 @@ export abstract class UmbWorkspaceContentContext< // TODO: consider turning this into an abstract so each context implement this them selfs. public save(): Promise { if (!this._store) { - // TODO: more beautiful error: + // TODO: add a more beautiful error: console.error('Could not save cause workspace context has no store.'); return Promise.resolve(); } diff --git a/src/Umbraco.Web.UI.Client/src/core/observable-api/append-to-frozen-array.method.ts b/src/Umbraco.Web.UI.Client/src/core/observable-api/append-to-frozen-array.method.ts new file mode 100644 index 0000000000..638008ffb9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/observable-api/append-to-frozen-array.method.ts @@ -0,0 +1,32 @@ +/** + * @export + * @method appendToFrozenArray + * @param {Observable} source - RxJS Subject to use for this Observable. + * @param {(mappable: T) => R} mappingFunction - Method to return the part for this Observable to return. + * @param {(previousResult: R, currentResult: R) => boolean} [memoizationFunction] - Method to Compare if the data has changed. Should return true when data is different. + * @description - Creates a RxJS Observable from RxJS Subject. + * @example Example append new entry for a UniqueBehaviorSubject which is an array. Where the key is unique and the item will be updated if matched with existing. + * const entry = {key: 'myKey', value: 'myValue'}; + * const newDataSet = appendToFrozenArray(mySubject.getValue(), entry, x => x.key === key); + * mySubject.next(newDataSet); + */ + + + + + +export function appendToFrozenArray(data: T[], entry: T, getUniqueMethod?: (entry: T) => unknown): T[] { + const unFrozenDataSet = [...data]; + if (getUniqueMethod) { + const unique = getUniqueMethod(entry); + const indexToReplace = unFrozenDataSet.findIndex((x) => getUniqueMethod(x) === unique); + if (indexToReplace !== -1) { + unFrozenDataSet[indexToReplace] = entry; + } else { + unFrozenDataSet.push(entry); + } + } else { + unFrozenDataSet.push(entry); + } + return unFrozenDataSet; +} diff --git a/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-array-behavior-subject.ts b/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-array-behavior-subject.ts index a34aa895c8..20519fc7b9 100644 --- a/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-array-behavior-subject.ts +++ b/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-array-behavior-subject.ts @@ -1,4 +1,5 @@ -import { appendToFrozenArray, UniqueBehaviorSubject } from "./unique-behavior-subject"; +import { UniqueBehaviorSubject } from "./unique-behavior-subject"; +import { appendToFrozenArray } from "./append-to-frozen-array.method"; /** * @export @@ -13,14 +14,38 @@ import { appendToFrozenArray, UniqueBehaviorSubject } from "./unique-behavior-su export class UniqueArrayBehaviorSubject extends UniqueBehaviorSubject { - constructor(initialData: T[], private _uniqueCompare?: (existingEntry: T, newEntry: T) => boolean) { + constructor(initialData: T[], private _getUnique?: (entry: T) => unknown) { super(initialData); } + /** + * @method append + * @param {unknown} unique - The unique value to remove. + * @description - Remove some new data of this Subject. + * @example Example remove entry with key '1' + * const data = [ + * { key: 1, value: 'foo'}, + * { key: 2, value: 'bar'} + * ]; + * const mySubject = new UniqueArrayBehaviorSubject(data, (x) => x.key); + * mySubject.remove(1); + */ + remove(unique: unknown) { + const unFrozenDataSet = [...this.getValue()]; + if (this._getUnique) { + unFrozenDataSet.filter(x => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return this._getUnique(x) !== unique; + }); + this.next(unFrozenDataSet); + } + } + /** * @method append * @param {Partial} partialData - A object containing some of the data for this Subject. - * @description - Append some new data to this Object. + * @description - Append some new data to this Subject. * @example Example append some data. * const data = [ * { key: 1, value: 'foo'}, @@ -30,13 +55,13 @@ export class UniqueArrayBehaviorSubject extends UniqueBehaviorSubject { * mySubject.append({ key: 1, value: 'replaced-foo'}); */ appendOne(entry: T) { - this.next(appendToFrozenArray(this.getValue(), entry, this._uniqueCompare)) + this.next(appendToFrozenArray(this.getValue(), entry, this._getUnique)) } /** * @method append * @param {T[]} entries - A array of new data to be added in this Subject. - * @description - Append some new data to this Object, if it compares to existing data it will replace it. + * @description - Append some new data to this Subject, if it compares to existing data it will replace it. * @example Example append some data. * const data = [ * { key: 1, value: 'foo'}, diff --git a/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-behavior-subject.ts b/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-behavior-subject.ts index 032ac040e8..20af12b3b1 100644 --- a/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-behavior-subject.ts +++ b/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-behavior-subject.ts @@ -28,36 +28,6 @@ export function naiveObjectComparison(objOne: any, objTwo: any): boolean { -/** - * @export - * @method appendToFrozenArray - * @param {Observable} source - RxJS Subject to use for this Observable. - * @param {(mappable: T) => R} mappingFunction - Method to return the part for this Observable to return. - * @param {(previousResult: R, currentResult: R) => boolean} [memoizationFunction] - Method to Compare if the data has changed. Should return true when data is different. - * @description - Creates a RxJS Observable from RxJS Subject. - * @example Example append new entry for a UniqueBehaviorSubject which is an array. Where the key is unique and the item will be updated if matched with existing. - * const entry = {key: 'myKey', value: 'myValue'}; - * const newDataSet = appendToFrozenArray(mySubject.getValue(), entry, x => x.key === key); - * mySubject.next(newDataSet); - */ -export function appendToFrozenArray(data: T[], entry: T, uniqueMethod?: (existingEntry: T, newEntry: T) => boolean): T[] { - const unFrozenDataSet = [...data]; - if(uniqueMethod) { - const indexToReplace = unFrozenDataSet.findIndex((x) => uniqueMethod(x, entry)); - if(indexToReplace !== -1) { - unFrozenDataSet[indexToReplace] = entry; - } else { - unFrozenDataSet.push(entry); - } - } else { - unFrozenDataSet.push(entry); - } - return unFrozenDataSet; -} - - - - export type MappingFunction = (mappable: T) => R; export type MemoizationFunction = (previousResult: R, currentResult: R) => boolean; From 2978d1072ee4194d99bd96315aedc52e982749f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 14:46:26 +0100 Subject: [PATCH 03/35] note --- .../workspace/workspace-content/workspace-content.context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts index 525f3b9c25..474b773663 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts @@ -119,7 +119,7 @@ export abstract class UmbWorkspaceContentContext< }); } - // TODO: how can we make sure to call this. + // TODO: how can we make sure to call this, we might need to turn this thing into a ContextProvider(extending) for it to call destroy? public destroy(): void { this._data.unsubscribe(); } From 8465793de7cb54b9e90effae38b056d8dfe191e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 14:55:56 +0100 Subject: [PATCH 04/35] correct types --- .../documents/documents/document.detail.store.ts | 2 +- .../workspace/document-workspace.context.ts | 10 +++++----- .../settings/data-types/data-type.store.ts | 5 +++-- .../shared/collection/collection.context.ts | 4 ++-- .../workspace-content/workspace-content.context.ts | 4 ++-- src/Umbraco.Web.UI.Client/src/core/stores/store.ts | 14 +++++++------- 6 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts index 484f6cc005..dd5b32b182 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts @@ -19,7 +19,7 @@ export const UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken { - private _data = new UniqueArrayBehaviorSubject([], (a, b) => a.key === b.key); + private _data = new UniqueArrayBehaviorSubject([], (x) => x.key); constructor(host: UmbControllerHostInterface) { 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 368d118371..edb54a7eb6 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 @@ -1,6 +1,6 @@ import { UmbWorkspaceContentContext } from '../../../shared/components/workspace/workspace-content/workspace-content.context'; -import { DOCUMENT_DETAIl_STORE_ALIAS } from '../document.detail.store'; -import type { UmbDocumentDetailStore, UmbDocumentStoreItemType } from '../document.detail.store'; +import { DOCUMENT_DETAIL_STORE_ALIAS } from '../document.detail.store'; +import type { UmbDocumentDetailStore } from '../document.detail.store'; import type { UmbControllerHostInterface } from '@umbraco-cms/controller'; import type { DocumentDetails } from '@umbraco-cms/models'; import { appendToFrozenArray } from '@umbraco-cms/observable-api'; @@ -32,11 +32,11 @@ const DefaultDocumentData = { name: '', }, ], -} as UmbDocumentStoreItemType; +} as DocumentDetails; -export class UmbWorkspaceDocumentContext extends UmbWorkspaceContentContext { +export class UmbWorkspaceDocumentContext extends UmbWorkspaceContentContext { constructor(host: UmbControllerHostInterface) { - super(host, DefaultDocumentData, DOCUMENT_DETAIl_STORE_ALIAS, 'document'); + super(host, DefaultDocumentData, DOCUMENT_DETAIL_STORE_ALIAS, 'document'); } public setPropertyValue(alias: string, value: unknown) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.store.ts index 7ffda6749a..e6708b35cd 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.store.ts @@ -1,9 +1,9 @@ import { map, Observable } from 'rxjs'; -import { UmbDataStoreBase } from '../../../core/stores/store'; import type { DataTypeDetails } from '@umbraco-cms/models'; import { DataTypeResource, FolderTreeItem } from '@umbraco-cms/backend-api'; import { tryExecuteAndNotify } from '@umbraco-cms/resources'; import { UmbContextToken } from '@umbraco-cms/context-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; const isDataTypeDetails = (dataType: DataTypeDetails | FolderTreeItem): dataType is DataTypeDetails => { return (dataType as DataTypeDetails).data !== undefined; @@ -22,7 +22,8 @@ export const STORE_ALIAS = 'UmbDataTypeStore'; * @extends {UmbDataStoreBase} * @description - Data Store for Data Types */ -export class UmbDataTypeStore extends UmbDataStoreBase { +export class UmbDataTypeStore extends UmbStoreBase { + public readonly storeAlias = STORE_ALIAS; /** diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts index 3bdcfe9ebc..8a23c626b1 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts @@ -1,11 +1,11 @@ import { ContentTreeItem } from '@umbraco-cms/backend-api'; -import { UmbTreeStore } from '@umbraco-cms/stores/store'; +import { UmbTreeDataStore } from '@umbraco-cms/stores/store'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbContextToken, UmbContextConsumerController } from '@umbraco-cms/context-api'; import { UniqueBehaviorSubject, UmbObserverController } from '@umbraco-cms/observable-api'; export class UmbCollectionContext< DataType extends ContentTreeItem, - StoreType extends UmbTreeStore = UmbTreeStore + StoreType extends UmbTreeDataStore = UmbTreeDataStore > { private _host: UmbControllerHostInterface; private _entityKey: string | null; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts index 474b773663..2fa4f7b445 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts @@ -10,8 +10,8 @@ import type { ContentDetails } from '@umbraco-cms/models'; // TODO: Consider if its right to have this many class-inheritance of WorkspaceContext // TODO: Could we extract this code into a 'Manager' of its own, which will be instantiated by the concrete Workspace Context. This will be more transparent and 'reuseable' export abstract class UmbWorkspaceContentContext< - ContentTypeType extends ContentDetails, - StoreType extends UmbContentStore + ContentTypeType extends ContentDetails = ContentDetails, + StoreType extends UmbContentStore = UmbContentStore > { protected _host: UmbControllerHostInterface; diff --git a/src/Umbraco.Web.UI.Client/src/core/stores/store.ts b/src/Umbraco.Web.UI.Client/src/core/stores/store.ts index c37274b158..d4f461b3c8 100644 --- a/src/Umbraco.Web.UI.Client/src/core/stores/store.ts +++ b/src/Umbraco.Web.UI.Client/src/core/stores/store.ts @@ -5,27 +5,27 @@ export interface UmbDataStoreIdentifiers { [more: string]: any; } -export interface UmbDataStore { +export interface UmbDataStore { readonly storeAlias: string; - // TODO: is items the right name? - readonly items: Observable>; - updateItems(items: Array): void; + // TODO: is it right to enforce these? Niels: Ill keep them out for now: + //readonly items: Observable>; + //updateItems(items: Array): void; } -export interface UmbTreeStore extends UmbDataStore { +export interface UmbTreeStore extends UmbDataStore { getTreeRoot(): Observable>; getTreeItemChildren(key: string): Observable>; } -export interface UmbContentStore extends UmbDataStore { +export interface UmbContentStore extends UmbDataStore { /** * @description - Request data by key. The data is added to the store and is returned as an Observable. * @param {string} key * @return {*} {(Observable)} * @memberof UmbDataStoreBase */ - getByKey(key: string): Observable; + getByKey(key: string): Observable; /** * @description - Save data. From d519ef6b96e12650dc1e864a2140f682dd60c6d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 15:06:19 +0100 Subject: [PATCH 05/35] media stores --- .../documents/document.detail.store.ts | 2 +- .../documents/document.tree.store.ts | 2 +- .../media/media/media.detail.store.ts | 83 +++++++++++++ .../src/backoffice/media/media/media.store.ts | 115 ------------------ .../media/media/media.tree.store.ts | 95 +++++++++++++++ 5 files changed, 180 insertions(+), 117 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.detail.store.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.store.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.tree.store.ts diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts index dd5b32b182..8d61a0aefc 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts @@ -77,6 +77,6 @@ export class UmbDocumentDetailStore extends UmbStoreBase implements UmbContentSt }, }); const data = await res.json(); - this._data.next(data); + this._data.append(data); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts index 0c2e2c5a91..8f581ad557 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts @@ -7,7 +7,7 @@ import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; -export const UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDocumentDetailStore'); +export const UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDocumentTreeStore'); /** diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.detail.store.ts new file mode 100644 index 0000000000..b9be387841 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.detail.store.ts @@ -0,0 +1,83 @@ +import { Observable } from 'rxjs'; +import type { DocumentDetails, MediaDetails } from '@umbraco-cms/models'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { UmbContentStore } from '@umbraco-cms/stores/store'; + + +export const UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDocumentDetailStore'); + + +/** + * @export + * @class UmbMediaStore + * @extends {UmbStoreBase} + * @description - Data Store for Media + */ +export class UmbMediaDetailStore extends UmbStoreBase implements UmbContentStore { + + + private _data = new UniqueArrayBehaviorSubject([], (x) => x.key); + + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN.toString()); + } + + getByKey(key: string): Observable { + // TODO: use backend cli when available. + fetch(`/umbraco/management/api/v1/media/details/${key}`) + .then((res) => res.json()) + .then((data) => { + this._data.append(data); + }); + + return createObservablePart(this._data, (documents) => + documents.find((document) => document.key === key) + ); + } + + // TODO: make sure UI somehow can follow the status of this action. + save(data: MediaDetails[]): Promise { + // fetch from server and update store + // TODO: use Fetcher API. + let body: string; + + try { + body = JSON.stringify(data); + } catch (error) { + console.error(error); + return Promise.reject(); + } + + // TODO: use backend cli when available. + return fetch('/umbraco/management/api/v1/media/save', { + method: 'POST', + body: body, + headers: { + 'Content-Type': 'application/json', + }, + }) + .then((res) => res.json()) + .then((data: Array) => { + this._data.append(data); + }); + } + + // TODO: how do we handle trashed items? + // TODO: How do we make trash available on details and tree store? + async trash(keys: Array) { + // TODO: use backend cli when available. + const res = await fetch('/umbraco/management/api/v1/media/trash', { + method: 'POST', + body: JSON.stringify(keys), + headers: { + 'Content-Type': 'application/json', + }, + }); + const data = await res.json(); + this._data.append(data); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.store.ts deleted file mode 100644 index fba7c229ae..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.store.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { map, Observable } from 'rxjs'; -import { UmbDataStoreBase } from '../../../core/stores/store'; -import type { MediaDetails } from '@umbraco-cms/models'; -import { ContentTreeItem, MediaResource } from '@umbraco-cms/backend-api'; -import { tryExecuteAndNotify } from '@umbraco-cms/resources'; -import { UmbContextToken } from '@umbraco-cms/context-api'; - -const isMediaDetails = (media: UmbMediaStoreItemType): media is MediaDetails => { - return (media as MediaDetails).data !== undefined; -}; - -// TODO: stop using ContentTreeItem. -export type UmbMediaStoreItemType = MediaDetails | ContentTreeItem; - -export const STORE_ALIAS = 'UmbMediaStore'; - -/** - * @export - * @class UmbMediaStore - * @extends {UmbMediaStoreBase} - * @description - Data Store for Media - */ -export class UmbMediaStore extends UmbDataStoreBase { - public readonly storeAlias = STORE_ALIAS; - - getByKey(key: string): Observable { - // fetch from server and update store - fetch(`/umbraco/management/api/v1/media/details/${key}`) - .then((res) => res.json()) - .then((data) => { - this.updateItems(data); - }); - - return this.items.pipe( - map((media) => (media.find((media) => media.key === key && isMediaDetails(media)) as MediaDetails) || null) - ); - } - - // TODO: make sure UI somehow can follow the status of this action. - save(data: MediaDetails[]): Promise { - // fetch from server and update store - // TODO: use Fetcher API. - let body: string; - - try { - body = JSON.stringify(data); - } catch (error) { - console.error(error); - return Promise.reject(); - } - - // TODO: Use node type to hit the right API, or have a general Node API? - return fetch('/umbraco/management/api/v1/media/save', { - method: 'POST', - body: body, - headers: { - 'Content-Type': 'application/json', - }, - }) - .then((res) => res.json()) - .then((data: Array) => { - this.updateItems(data); - }); - } - - // TODO: how do we handle trashed items? - async trash(keys: Array) { - // fetch from server and update store - // TODO: Use node type to hit the right API, or have a general Node API? - const res = await fetch('/umbraco/management/api/v1/media/trash', { - method: 'POST', - body: JSON.stringify(keys), - headers: { - 'Content-Type': 'application/json', - }, - }); - const data = await res.json(); - this.updateItems(data); - } - - getTreeRoot(): Observable> { - tryExecuteAndNotify(this.host, MediaResource.getTreeMediaRoot({})).then(({ data }) => { - if (data) { - this.updateItems(data.items); - } - }); - - // TODO: how do we handle trashed items? - // TODO: remove ignore when we know how to handle trashed items. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return this.items.pipe(map((items) => items.filter((item) => item.parentKey === null && !item.isTrashed))); - } - - getTreeItemChildren(key: string): Observable> { - tryExecuteAndNotify( - this.host, - MediaResource.getTreeMediaChildren({ - parentKey: key, - }) - ).then(({ data }) => { - if (data) { - this.updateItems(data.items); - } - }); - - // TODO: how do we handle trashed items? - // TODO: remove ignore when we know how to handle trashed items. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return this.items.pipe(map((items) => items.filter((item) => item.parentKey === key && !item.isTrashed))); - } -} - -export const UMB_MEDIA_STORE_CONTEXT_TOKEN = new UmbContextToken(STORE_ALIAS); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.tree.store.ts new file mode 100644 index 0000000000..c6801c3794 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.tree.store.ts @@ -0,0 +1,95 @@ +import type { Observable } from 'rxjs'; +import { MediaResource, ContentTreeItem } from '@umbraco-cms/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; + + +export const UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbMediaTreeStore'); + +// TODO: Stop using ContentTreeItem +type MediaTreeItem = ContentTreeItem; + +/** + * @export + * @class UmbMediaTreeStore + * @extends {UmbStoreBase} + * @description - Data Store for Media + */ +export class UmbMediaTreeStore extends UmbStoreBase { + + + + private _data = new UniqueArrayBehaviorSubject([], (x) => x.key); + + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN.toString()); + } + + // TODO: how do we handle trashed items? + // TODO: How do we make trash available on details and tree store? + async trash(keys: Array) { + // TODO: use backend cli when available. + const res = await fetch('/umbraco/management/api/v1/media/trash', { + method: 'POST', + body: JSON.stringify(keys), + headers: { + 'Content-Type': 'application/json', + }, + }); + const data = await res.json(); + this._data.append(data); + } + + getTreeRoot(): Observable> { + tryExecuteAndNotify(this._host, MediaResource.getTreeMediaRoot({})).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: how do we handle trashed items? + // TODO: remove ignore when we know how to handle trashed items. + return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === null && !item.isTrashed)); + } + + getTreeItemChildren(key: string): Observable> { + tryExecuteAndNotify( + this._host, + MediaResource.getTreeMediaChildren({ + 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); + } + }); + + // TODO: how do we handle trashed items? + // TODO: remove ignore when we know how to handle trashed items. + return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === key && !item.isTrashed)); + } + + getTreeItems(keys: Array): Observable> { + if (keys?.length > 0) { + tryExecuteAndNotify( + this._host, + MediaResource.getTreeMediaItem({ + 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 createObservablePart(this._data, (items) => items.filter((item) => keys.includes(item.key ?? ''))); + } +} From c3fc6994f2a2dabb08e34ac20427a0c1c4e2b745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 15:12:44 +0100 Subject: [PATCH 06/35] self registrations --- .../src/backoffice/backoffice.element.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) 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 241cab9d39..bf9537a1a0 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts @@ -18,8 +18,10 @@ import { } from './documents/document-types/document-type.store'; import { UmbMediaTypeStore, UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN } from './media/media-types/media-type.store'; import { UmbMemberTypeStore, UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN } from './members/member-types/member-type.store'; -import { UmbDocumentStore, UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN } from './documents/documents/document.detail.store'; -import { UmbMediaStore, UMB_MEDIA_STORE_CONTEXT_TOKEN } from './media/media/media.store'; +import { UmbDocumentDetailStore } from './documents/documents/document.detail.store'; +import { UmbDocumentTreeStore } from './documents/documents/document.tree.store'; +import { UmbMediaDetailStore } from './media/media/media.detail.store'; +import { UmbMediaTreeStore } from './media/media/media.tree.store'; import { UmbMemberGroupStore, UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN } from './members/member-groups/member-group.store'; import { UmbDictionaryStore, UMB_DICTIONARY_STORE_CONTEXT_TOKEN } from './translation/dictionary/dictionary.store'; import { @@ -68,9 +70,11 @@ export class UmbBackofficeElement extends UmbLitElement { // TODO: find a way this is possible outside this element. It needs to be possible to register stores in extensions this.provideContext(UMB_CURRENT_USER_STORE_CONTEXT_TOKEN, new UmbCurrentUserStore()); - new UmbDocumentStore(this); + new UmbDocumentDetailStore(this); + new UmbDocumentTreeStore(this); + new UmbMediaDetailStore(this); + new UmbMediaTreeStore(this); - this.provideContext(UMB_MEDIA_STORE_CONTEXT_TOKEN, new UmbMediaStore(this)); this.provideContext(UMB_DATA_TYPE_STORE_CONTEXT_TOKEN, new UmbDataTypeStore(this)); this.provideContext(UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN, new UmbDocumentTypeStore(this)); this.provideContext(UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN, new UmbMediaTypeStore(this)); From 552a5e5ab5d2dac492c354a76c3eba93d759cadb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 15:16:43 +0100 Subject: [PATCH 07/35] media stores --- .../src/backoffice/media/media/tree/manifests.ts | 4 ++-- .../backoffice/media/media/workspace/manifests.ts | 4 ++-- .../media/workspace/media-workspace.context.ts | 14 +++++++------- .../src/backoffice/media/section.manifests.ts | 4 ++-- .../dashboards/dashboard-collection.element.ts | 6 +++--- .../workspace-view-collection.element.ts | 5 +++-- .../src/core/observable-api/index.ts | 1 + 7 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/tree/manifests.ts index 5160b0f9cf..9ab486dfda 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/tree/manifests.ts @@ -1,4 +1,4 @@ -import { STORE_ALIAS } from '../media.store'; +import { UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN } from '../media.tree.store'; import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models'; const treeAlias = 'Umb.Tree.Media'; @@ -8,7 +8,7 @@ const tree: ManifestTree = { alias: treeAlias, name: 'Media Tree', meta: { - storeAlias: STORE_ALIAS, + storeAlias: UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN.toString(), }, }; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/manifests.ts index 0814d0f8e9..91da78515e 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/manifests.ts @@ -4,7 +4,7 @@ import type { ManifestWorkspaceView, ManifestWorkspaceViewCollection, } from '@umbraco-cms/models'; -import { STORE_ALIAS } from '../media.store'; +import { UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN } from '../media.detail.store'; const workspace: ManifestWorkspace = { type: 'workspace', @@ -59,7 +59,7 @@ const workspaceViewCollections: Array = [ pathname: 'collection', icon: 'umb:grid', entityType: 'media', - storeAlias: STORE_ALIAS, + storeAlias: UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN.toString(), }, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/media-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/media-workspace.context.ts index 490ad41680..c724271abb 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/media-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/workspace/media-workspace.context.ts @@ -1,10 +1,10 @@ import { UmbWorkspaceContentContext } from '../../../shared/components/workspace/workspace-content/workspace-content.context'; import { - UmbMediaStore, - UmbMediaStoreItemType, - UMB_MEDIA_STORE_CONTEXT_TOKEN, -} from 'src/backoffice/media/media/media.store'; + UmbMediaDetailStore, + UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN, +} from 'src/backoffice/media/media/media.detail.store'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import type { MediaDetails } from '@umbraco-cms/models'; const DefaultMediaData = { key: '', @@ -33,11 +33,11 @@ const DefaultMediaData = { name: '', }, ], -} as UmbMediaStoreItemType; +} as MediaDetails; -export class UmbWorkspaceMediaContext extends UmbWorkspaceContentContext { +export class UmbWorkspaceMediaContext extends UmbWorkspaceContentContext { constructor(host: UmbControllerHostInterface) { - super(host, DefaultMediaData, UMB_MEDIA_STORE_CONTEXT_TOKEN.toString(), 'media'); + super(host, DefaultMediaData, UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN.toString(), 'media'); } public setPropertyValue(alias: string, value: unknown) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/section.manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/section.manifests.ts index b6ae3a143c..81c1dbd762 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/section.manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/section.manifests.ts @@ -1,4 +1,4 @@ -import { STORE_ALIAS } from './media/media.store'; +import { UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN } from './media/media.detail.store'; import type { ManifestDashboardCollection, ManifestSection } from '@umbraco-cms/models'; const sectionAlias = 'Umb.Section.Media'; @@ -25,7 +25,7 @@ const dashboards: Array = [ sections: [sectionAlias], pathname: 'media-management', entityType: 'media', - storeAlias: STORE_ALIAS, + storeAlias: UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN.toString(), }, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/dashboards/dashboard-collection.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/dashboards/dashboard-collection.element.ts index 97719a81ea..22426767bd 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/dashboards/dashboard-collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/dashboards/dashboard-collection.element.ts @@ -3,12 +3,12 @@ import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import '../collection.element'; import { ifDefined } from 'lit-html/directives/if-defined.js'; -import { UmbMediaStore, UmbMediaStoreItemType } from 'src/backoffice/media/media/media.store'; +import { UmbMediaDetailStore } from 'src/backoffice/media/media/media.detail.store'; import { UmbCollectionContext, UMB_COLLECTION_CONTEXT_TOKEN, } from 'src/backoffice/shared/collection/collection.context'; -import type { ManifestDashboardCollection } from '@umbraco-cms/models'; +import type { ManifestDashboardCollection, MediaDetails } from '@umbraco-cms/models'; import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-dashboard-collection') @@ -26,7 +26,7 @@ export class UmbDashboardCollectionElement extends UmbLitElement { `, ]; - private _collectionContext?: UmbCollectionContext; + private _collectionContext?: UmbCollectionContext; public manifest!: ManifestDashboardCollection; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/collection/workspace-view-collection.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/collection/workspace-view-collection.element.ts index 882dc1bdf9..655b308977 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/collection/workspace-view-collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/collection/workspace-view-collection.element.ts @@ -7,11 +7,12 @@ import { UmbCollectionContext, UMB_COLLECTION_CONTEXT_TOKEN, } from 'src/backoffice/shared/collection/collection.context'; -import { UmbMediaStore, UmbMediaStoreItemType } from 'src/backoffice/media/media/media.store'; +import { UmbMediaDetailStore } from 'src/backoffice/media/media/media.detail.store'; import '../../../../../../shared/components/content-property/content-property.element'; import '../../../../../../shared/collection/dashboards/dashboard-collection.element'; import { UmbLitElement } from '@umbraco-cms/element'; +import type { MediaDetails } from '@umbraco-cms/models'; @customElement('umb-workspace-view-collection') export class UmbWorkspaceViewCollectionElement extends UmbLitElement { @@ -27,7 +28,7 @@ export class UmbWorkspaceViewCollectionElement extends UmbLitElement { private _workspaceContext?: UmbWorkspaceContentContext; - private _collectionContext?: UmbCollectionContext; + private _collectionContext?: UmbCollectionContext; constructor() { super(); diff --git a/src/Umbraco.Web.UI.Client/src/core/observable-api/index.ts b/src/Umbraco.Web.UI.Client/src/core/observable-api/index.ts index 8b9c23fdbd..a742f8b666 100644 --- a/src/Umbraco.Web.UI.Client/src/core/observable-api/index.ts +++ b/src/Umbraco.Web.UI.Client/src/core/observable-api/index.ts @@ -4,3 +4,4 @@ export * from './unique-behavior-subject'; export * from './unique-array-behavior-subject'; export * from './unique-object-behavior-subject'; export * from './create-observable-part.method' +export * from './append-to-frozen-array.method' From 9d5b0befbce7e3cfca61c6674197847abbb082c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 15:27:40 +0100 Subject: [PATCH 08/35] simpler types --- .../backoffice/documents/documents/document.detail.store.ts | 5 ++--- .../backoffice/documents/documents/document.tree.store.ts | 6 +++--- .../src/backoffice/media/media/media.detail.store.ts | 5 ++--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts index 8d61a0aefc..3a03b40656 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts @@ -1,4 +1,3 @@ -import { Observable } from 'rxjs'; import type { DocumentDetails } from '@umbraco-cms/models'; import { UmbContextToken } from '@umbraco-cms/context-api'; import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; @@ -26,7 +25,7 @@ export class UmbDocumentDetailStore extends UmbStoreBase implements UmbContentSt super(host, UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN.toString()); } - getByKey(key: string): Observable { + getByKey(key: string) { // TODO: use backend cli when available. fetch(`/umbraco/management/api/v1/document/details/${key}`) .then((res) => res.json()) @@ -40,7 +39,7 @@ export class UmbDocumentDetailStore extends UmbStoreBase implements UmbContentSt } // TODO: make sure UI somehow can follow the status of this action. - save(data: DocumentDetails[]): Promise { + save(data: DocumentDetails[]) { // fetch from server and update store // TODO: use Fetcher API. let body: string; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts index 8f581ad557..b6015d47ff 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts @@ -40,7 +40,7 @@ export class UmbDocumentTreeStore extends UmbStoreBase { this._data.append(data); } - getTreeRoot(): Observable> { + getTreeRoot() { tryExecuteAndNotify(this._host, DocumentResource.getTreeDocumentRoot({})).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)? @@ -53,7 +53,7 @@ export class UmbDocumentTreeStore extends UmbStoreBase { return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === null && !item.isTrashed)); } - getTreeItemChildren(key: string): Observable> { + getTreeItemChildren(key: string) { tryExecuteAndNotify( this._host, DocumentResource.getTreeDocumentChildren({ @@ -71,7 +71,7 @@ export class UmbDocumentTreeStore extends UmbStoreBase { return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === key && !item.isTrashed)); } - getTreeItems(keys: Array): Observable> { + getTreeItems(keys: Array) { if (keys?.length > 0) { tryExecuteAndNotify( this._host, diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.detail.store.ts index b9be387841..646dc36273 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.detail.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.detail.store.ts @@ -1,4 +1,3 @@ -import { Observable } from 'rxjs'; import type { DocumentDetails, MediaDetails } from '@umbraco-cms/models'; import { UmbContextToken } from '@umbraco-cms/context-api'; import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; @@ -26,7 +25,7 @@ export class UmbMediaDetailStore extends UmbStoreBase implements UmbContentStore super(host, UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN.toString()); } - getByKey(key: string): Observable { + getByKey(key: string) { // TODO: use backend cli when available. fetch(`/umbraco/management/api/v1/media/details/${key}`) .then((res) => res.json()) @@ -40,7 +39,7 @@ export class UmbMediaDetailStore extends UmbStoreBase implements UmbContentStore } // TODO: make sure UI somehow can follow the status of this action. - save(data: MediaDetails[]): Promise { + save(data: MediaDetails[]) { // fetch from server and update store // TODO: use Fetcher API. let body: string; From 090e2fbc2cd14e81e5d2aca595d1bacf31db68bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 15:47:46 +0100 Subject: [PATCH 09/35] data type --- .../documents/document.detail.store.ts | 2 +- .../documents/document.tree.store.ts | 3 +- .../media/media/media.detail.store.ts | 2 +- .../media/media/media.tree.store.ts | 2 +- .../data-types/data-type.detail.store.ts | 99 +++++++++++++++++++ .../data-types/data-type.tree.store.ts | 96 ++++++++++++++++++ .../unique-array-behavior-subject.ts | 17 ++-- 7 files changed, 209 insertions(+), 12 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.detail.store.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.tree.store.ts diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts index 3a03b40656..ce52e59454 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.detail.store.ts @@ -12,7 +12,7 @@ export const UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken} + * @extends {UmbStoreBase} * @description - Data Store for Documents */ export class UmbDocumentDetailStore extends UmbStoreBase implements UmbContentStore { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts index b6015d47ff..43d53ad842 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/document.tree.store.ts @@ -1,4 +1,3 @@ -import type { Observable } from 'rxjs'; import { DocumentResource, DocumentTreeItem } from '@umbraco-cms/backend-api'; import { tryExecuteAndNotify } from '@umbraco-cms/resources'; import { UmbContextToken } from '@umbraco-cms/context-api'; @@ -13,7 +12,7 @@ export const UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken} + * @extends {UmbStoreBase} * @description - Data Store for Documents */ export class UmbDocumentTreeStore extends UmbStoreBase { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.detail.store.ts index 646dc36273..5a9403138a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.detail.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.detail.store.ts @@ -12,7 +12,7 @@ export const UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken} + * @extends {UmbStoreBase} * @description - Data Store for Media */ export class UmbMediaDetailStore extends UmbStoreBase implements UmbContentStore { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.tree.store.ts index c6801c3794..36f1a3c9cd 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.tree.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.tree.store.ts @@ -15,7 +15,7 @@ type MediaTreeItem = ContentTreeItem; /** * @export * @class UmbMediaTreeStore - * @extends {UmbStoreBase} + * @extends {UmbStoreBase} * @description - Data Store for Media */ export class UmbMediaTreeStore extends UmbStoreBase { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.detail.store.ts new file mode 100644 index 0000000000..31b5035a34 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.detail.store.ts @@ -0,0 +1,99 @@ +import type { DataTypeDetails } from '@umbraco-cms/models'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { DataTypeResource } from '@umbraco-cms/backend-api'; + + +export const UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDataTypeDetailStore'); + + +/** + * @export + * @class UmbDataTypeDetailStore + * @extends {UmbStoreBase} + * @description - Details Data Store for Data Types + */ +export class UmbDataTypeDetailStore extends UmbStoreBase { + + + private _data = new UniqueArrayBehaviorSubject([], (x) => x.key); + + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN.toString()); + } + + /** + * @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 UmbDataTypesStore + */ + getByKey(key: string) { + // TODO: use backend cli when available. + fetch(`/umbraco/management/api/v1/document/data-type/${key}`) + .then((res) => res.json()) + .then((data) => { + this._data.append(data); + }); + + return createObservablePart(this._data, (documents) => + documents.find((document) => document.key === key) + ); + } + + // TODO: make sure UI somehow can follow the status of this action. + /** + * @description - Save a Data Type. + * @param {Array} dataTypes + * @memberof UmbDataTypesStore + * @return {*} {Promise} + */ + save(data: DataTypeDetails[]) { + // fetch from server and update store + // TODO: use Fetcher API. + let body: string; + + try { + body = JSON.stringify(data); + } catch (error) { + console.error(error); + return Promise.reject(); + } + + // TODO: use backend cli when available. + return fetch('/umbraco/management/api/v1/data-type/save', { + method: 'POST', + body: body, + headers: { + 'Content-Type': 'application/json', + }, + }) + .then((res) => res.json()) + .then((data: Array) => { + this._data.append(data); + }); + } + + // TODO: How can we avoid having this in both stores? + /** + * @description - Delete a Data Type. + * @param {string[]} keys + * @memberof UmbDataTypesStore + * @return {*} {Promise} + */ + async delete(keys: string[]) { + // TODO: use backend cli when available. + await fetch('/umbraco/backoffice/data-type/delete', { + method: 'POST', + body: JSON.stringify(keys), + headers: { + 'Content-Type': 'application/json', + }, + }); + + this._data.remove(keys); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.tree.store.ts new file mode 100644 index 0000000000..2e8b6aaa47 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.tree.store.ts @@ -0,0 +1,96 @@ +import { DataTypeResource, DocumentTreeItem } from '@umbraco-cms/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; + + +export const UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDocumentTreeStore'); + + +/** + * @export + * @class UmbDocumentStore + * @extends {UmbStoreBase} + * @description - Tree Data Store for Data Types + */ +export class UmbDocumentTreeStore extends UmbStoreBase { + + + private _data = new UniqueArrayBehaviorSubject([], (x) => x.key); + + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN.toString()); + } + + // TODO: How can we avoid having this in both stores? + /** + * @description - Delete a Data Type. + * @param {string[]} keys + * @memberof UmbDataTypesStore + * @return {*} {Promise} + */ + async delete(keys: string[]) { + // TODO: use backend cli when available. + await fetch('/umbraco/backoffice/data-type/delete', { + method: 'POST', + body: JSON.stringify(keys), + headers: { + 'Content-Type': 'application/json', + }, + }); + + this._data.remove(keys); + } + + getTreeRoot() { + tryExecuteAndNotify(this._host, DataTypeResource.getTreeDataTypeRoot({})).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: how do we handle trashed items? + // TODO: remove ignore when we know how to handle trashed items. + return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === null && !item.isTrashed)); + } + + getTreeItemChildren(key: string) { + tryExecuteAndNotify( + this._host, + DataTypeResource.getTreeDataTypeChildren({ + 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); + } + }); + + // TODO: how do we handle trashed items? + // TODO: remove ignore when we know how to handle trashed items. + return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === key && !item.isTrashed)); + } + + getTreeItems(keys: Array) { + if (keys?.length > 0) { + tryExecuteAndNotify( + this._host, + DataTypeResource.getTreeDataTypeItem({ + 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 createObservablePart(this._data, (items) => items.filter((item) => keys.includes(item.key ?? ''))); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-array-behavior-subject.ts b/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-array-behavior-subject.ts index 20519fc7b9..94267f6379 100644 --- a/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-array-behavior-subject.ts +++ b/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-array-behavior-subject.ts @@ -28,16 +28,19 @@ export class UniqueArrayBehaviorSubject extends UniqueBehaviorSubject { * { key: 2, value: 'bar'} * ]; * const mySubject = new UniqueArrayBehaviorSubject(data, (x) => x.key); - * mySubject.remove(1); + * mySubject.remove([1]); */ - remove(unique: unknown) { + remove(uniques: unknown[]) { const unFrozenDataSet = [...this.getValue()]; if (this._getUnique) { - unFrozenDataSet.filter(x => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return this._getUnique(x) !== unique; - }); + uniques.forEach( unique => + unFrozenDataSet.filter(x => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return this._getUnique(x) !== unique; + }) + ); + this.next(unFrozenDataSet); } } From c0338de5d0990a3fcc4ca0eae7008d5fbf8e32e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 20:51:32 +0100 Subject: [PATCH 10/35] user store first part of refactoring --- .../src/backoffice/users/users/user.store.ts | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/user.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/user.store.ts index 7399a86e63..34722f93f1 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/user.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/user.store.ts @@ -1,8 +1,8 @@ -import { map, Observable } from 'rxjs'; -import { UmbDataStoreBase } from '../../../core/stores/store'; +import { BehaviorSubject, map, Observable } from 'rxjs'; import type { UserDetails } from '@umbraco-cms/models'; -import { UniqueBehaviorSubject } from '@umbraco-cms/observable-api'; +import { createObservablePart, UniqueArrayBehaviorSubject, UniqueBehaviorSubject } from '@umbraco-cms/observable-api'; import { UmbContextToken } from '@umbraco-cms/context-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; export type UmbUserStoreItemType = UserDetails; @@ -14,10 +14,13 @@ export const STORE_ALIAS = 'UmbUserStore'; * @extends {UmbDataStoreBase} * @description - Data Store for Users */ -export class UmbUserStore extends UmbDataStoreBase { - public readonly storeAlias = STORE_ALIAS; - #totalUsers = new UniqueBehaviorSubject(0); +export class UmbUserStore extends UmbStoreBase { + + #users = new UniqueArrayBehaviorSubject([]); + public users = this.#users.asObservable(); + + #totalUsers = new BehaviorSubject(0); public readonly totalUsers = this.#totalUsers.asObservable(); getAll(): Observable> { @@ -27,14 +30,14 @@ export class UmbUserStore extends UmbDataStoreBase { .then((res) => res.json()) .then((data) => { this.#totalUsers.next(data.total); - this.updateItems(data.items); + this.#users.next(data.items); }); - return this.items; + return this.users; } /** - * @description - Request a Data Type by key. The Data Type is added to the store and is returned as an Observable. + * @description - Request a User by key. The User is added to the store and is returned as an Observable. * @param {string} key * @return {*} {(Observable)} * @memberof UmbDataTypeStore @@ -45,12 +48,10 @@ export class UmbUserStore extends UmbDataStoreBase { fetch(`/umbraco/backoffice/users/details/${key}`) .then((res) => res.json()) .then((data) => { - this.updateItems([data]); + this.#users.appendOne(data); }); - return this.items.pipe( - map((items: Array) => items.find((node: UmbUserStoreItemType) => node.key === key) || null) - ); + return createObservablePart(this.#users, (users: Array) => users.find((user: UmbUserStoreItemType) => user.key === key) || null); } getByKeys(keys: Array): Observable> { @@ -58,12 +59,10 @@ export class UmbUserStore extends UmbDataStoreBase { fetch(`/umbraco/backoffice/users/getByKeys?${params}`) .then((res) => res.json()) .then((data) => { - this.updateItems(data); + this.#users.append(data); }); - return this.items.pipe( - map((items: Array) => items.filter((node: UmbUserStoreItemType) => keys.includes(node.key))) - ); + return createObservablePart(this.#users, (users: Array) => users.filter((user: UmbUserStoreItemType) => keys.includes(user.key))); } getByName(name: string): Observable> { @@ -74,16 +73,14 @@ export class UmbUserStore extends UmbDataStoreBase { fetch(`/umbraco/backoffice/users/getByName?${params}`) .then((res) => res.json()) .then((data) => { - this.updateItems(data); + this.#users.append(data); }); - return this.items.pipe( - map((items: Array) => - items.filter((node: UserDetails) => node.name.toLocaleLowerCase().includes(name)) - ) - ); + return createObservablePart(this.#users, (users: Array) => users.filter((user: UmbUserStoreItemType) => user.name.toLocaleLowerCase().includes(name))); } + // TODO: Continue here: + async enableUsers(userKeys: Array): Promise { // TODO: use Fetcher API. try { From 7e60335b4f29347c9cd8bfd5067cc3b7061c5924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 21:14:55 +0100 Subject: [PATCH 11/35] user store refactor --- .../src/backoffice/users/users/user.store.ts | 59 ++++++++++--------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/user.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/user.store.ts index 34722f93f1..c4e85f2d82 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/user.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/user.store.ts @@ -1,6 +1,6 @@ -import { BehaviorSubject, map, Observable } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; import type { UserDetails } from '@umbraco-cms/models'; -import { createObservablePart, UniqueArrayBehaviorSubject, UniqueBehaviorSubject } from '@umbraco-cms/observable-api'; +import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; import { UmbContextToken } from '@umbraco-cms/context-api'; import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; @@ -17,13 +17,13 @@ export const STORE_ALIAS = 'UmbUserStore'; export class UmbUserStore extends UmbStoreBase { - #users = new UniqueArrayBehaviorSubject([]); + #users = new UniqueArrayBehaviorSubject([], x => x.key); public users = this.#users.asObservable(); #totalUsers = new BehaviorSubject(0); public readonly totalUsers = this.#totalUsers.asObservable(); - getAll(): Observable> { + getAll() { // TODO: use Fetcher API. // TODO: only fetch if the data type is not in the store? fetch(`/umbraco/backoffice/users/list/items`) @@ -42,7 +42,7 @@ export class UmbUserStore extends UmbStoreBase { * @return {*} {(Observable)} * @memberof UmbDataTypeStore */ - getByKey(key: string): Observable { + getByKey(key: string) { // TODO: use Fetcher API. // TODO: only fetch if the data type is not in the store? fetch(`/umbraco/backoffice/users/details/${key}`) @@ -54,7 +54,14 @@ export class UmbUserStore extends UmbStoreBase { return createObservablePart(this.#users, (users: Array) => users.find((user: UmbUserStoreItemType) => user.key === key) || null); } - getByKeys(keys: Array): Observable> { + + /** + * @description - Request Users by keys. + * @param {string} key + * @return {*} {(Observable)} + * @memberof UmbDataTypeStore + */ + getByKeys(keys: Array) { const params = keys.map((key) => `key=${key}`).join('&'); fetch(`/umbraco/backoffice/users/getByKeys?${params}`) .then((res) => res.json()) @@ -65,7 +72,7 @@ export class UmbUserStore extends UmbStoreBase { return createObservablePart(this.#users, (users: Array) => users.filter((user: UmbUserStoreItemType) => keys.includes(user.key))); } - getByName(name: string): Observable> { + getByName(name: string) { name = name.trim(); name = name.toLocaleLowerCase(); @@ -79,9 +86,7 @@ export class UmbUserStore extends UmbStoreBase { return createObservablePart(this.#users, (users: Array) => users.filter((user: UmbUserStoreItemType) => user.name.toLocaleLowerCase().includes(name))); } - // TODO: Continue here: - - async enableUsers(userKeys: Array): Promise { + async enableUsers(userKeys: Array) { // TODO: use Fetcher API. try { const res = await fetch('/umbraco/backoffice/users/enable', { @@ -92,19 +97,19 @@ export class UmbUserStore extends UmbStoreBase { }, }); const enabledKeys = await res.json(); - const storedUsers = this._items.getValue().filter((user) => enabledKeys.includes(user.key)); + const storedUsers = this.#users.getValue().filter((user) => enabledKeys.includes(user.key)); storedUsers.forEach((user) => { user.status = 'enabled'; }); - this.updateItems(storedUsers); + this.#users.append(storedUsers); } catch (error) { console.error('Enable Users failed', error); } } - async updateUserGroup(userKeys: Array, userGroup: string): Promise { + async updateUserGroup(userKeys: Array, userGroup: string) { // TODO: use Fetcher API. try { const res = await fetch('/umbraco/backoffice/users/updateUserGroup', { @@ -115,7 +120,7 @@ export class UmbUserStore extends UmbStoreBase { }, }); const enabledKeys = await res.json(); - const storedUsers = this._items.getValue().filter((user) => enabledKeys.includes(user.key)); + const storedUsers = this.#users.getValue().filter((user) => enabledKeys.includes(user.key)); storedUsers.forEach((user) => { if (userKeys.includes(user.key)) { @@ -125,13 +130,13 @@ export class UmbUserStore extends UmbStoreBase { } }); - this.updateItems(storedUsers); + this.#users.append(storedUsers); } catch (error) { console.error('Add user group failed', error); } } - async removeUserGroup(userKeys: Array, userGroup: string): Promise { + async removeUserGroup(userKeys: Array, userGroup: string) { // TODO: use Fetcher API. try { const res = await fetch('/umbraco/backoffice/users/enable', { @@ -142,19 +147,19 @@ export class UmbUserStore extends UmbStoreBase { }, }); const enabledKeys = await res.json(); - const storedUsers = this._items.getValue().filter((user) => enabledKeys.includes(user.key)); + const storedUsers = this.#users.getValue().filter((user) => enabledKeys.includes(user.key)); storedUsers.forEach((user) => { user.userGroups = user.userGroups.filter((group) => group !== userGroup); }); - this.updateItems(storedUsers); + this.#users.append(storedUsers); } catch (error) { console.error('Remove user group failed', error); } } - async disableUsers(userKeys: Array): Promise { + async disableUsers(userKeys: Array) { // TODO: use Fetcher API. try { const res = await fetch('/umbraco/backoffice/users/disable', { @@ -165,19 +170,19 @@ export class UmbUserStore extends UmbStoreBase { }, }); const disabledKeys = await res.json(); - const storedUsers = this._items.getValue().filter((user) => disabledKeys.includes(user.key)); + const storedUsers = this.#users.getValue().filter((user) => disabledKeys.includes(user.key)); storedUsers.forEach((user) => { user.status = 'disabled'; }); - this.updateItems(storedUsers); + this.#users.append(storedUsers); } catch (error) { console.error('Disable Users failed', error); } } - async deleteUsers(userKeys: Array): Promise { + async deleteUsers(userKeys: Array) { // TODO: use Fetcher API. try { const res = await fetch('/umbraco/backoffice/users/delete', { @@ -188,13 +193,13 @@ export class UmbUserStore extends UmbStoreBase { }, }); const deletedKeys = await res.json(); - this.deleteItems(deletedKeys); + this.#users.remove(deletedKeys); } catch (error) { console.error('Delete Users failed', error); } } - async save(users: Array): Promise { + async save(users: Array) { // TODO: use Fetcher API. try { const res = await fetch('/umbraco/backoffice/users/save', { @@ -205,7 +210,7 @@ export class UmbUserStore extends UmbStoreBase { }, }); const json = await res.json(); - this.updateItems(json); + this.#users.append(json); } catch (error) { console.error('Save Data Type error', error); } @@ -216,7 +221,7 @@ export class UmbUserStore extends UmbStoreBase { email: string, message: string, userGroups: Array - ): Promise { + ) { // TODO: use Fetcher API. try { const res = await fetch('/umbraco/backoffice/users/invite', { @@ -227,7 +232,7 @@ export class UmbUserStore extends UmbStoreBase { }, }); const json = (await res.json()) as UmbUserStoreItemType[]; - this.updateItems(json); + this.#users.append(json); return json[0]; } catch (error) { console.error('Invite user error', error); From 611c563b0806d50e4945c4ca6a46ab59239a1e1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 21:15:39 +0100 Subject: [PATCH 12/35] correct wording --- .../src/backoffice/users/users/user.store.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/user.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/user.store.ts index c4e85f2d82..fa95e5d89b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/user.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/user.store.ts @@ -11,7 +11,7 @@ export const STORE_ALIAS = 'UmbUserStore'; /** * @export * @class UmbUserStore - * @extends {UmbDataStoreBase} + * @extends {UmbStoreBase} * @description - Data Store for Users */ @@ -212,7 +212,7 @@ export class UmbUserStore extends UmbStoreBase { const json = await res.json(); this.#users.append(json); } catch (error) { - console.error('Save Data Type error', error); + console.error('Save user error', error); } } From ddea5b591f1850be7248423eeda90306224f0b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 21:19:44 +0100 Subject: [PATCH 13/35] data-type store --- .../.storybook/preview.js | 4 +- .../src/backoffice/backoffice.element.ts | 8 +- .../settings/data-types/data-type.store.ts | 130 ------------------ .../data-types/data-type.tree.store.ts | 10 +- 4 files changed, 12 insertions(+), 140 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.store.ts diff --git a/src/Umbraco.Web.UI.Client/.storybook/preview.js b/src/Umbraco.Web.UI.Client/.storybook/preview.js index 9eb279f45b..b0a8b8e2d0 100644 --- a/src/Umbraco.Web.UI.Client/.storybook/preview.js +++ b/src/Umbraco.Web.UI.Client/.storybook/preview.js @@ -12,7 +12,7 @@ import { initialize, mswDecorator } from 'msw-storybook-addon'; import { setCustomElements } from '@storybook/web-components'; import customElementManifests from '../custom-elements.json'; -import { STORE_ALIAS as dataTypeAlias, UmbDataTypeStore } from '../src/backoffice/settings/data-types/data-type.store'; +import { UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN, UmbDataTypeStore } from '../src/backoffice/settings/data-types/data-type.detail.store'; import { UmbDocumentTypeStore } from '../src/backoffice/documents/document-types/document-type.store'; import { UmbIconStore } from '../src/core/stores/icon/icon.store'; import { onUnhandledRequest } from '../src/core/mocks/browser'; @@ -53,7 +53,7 @@ customElements.define('umb-storybook', UmbStoryBookElement); const storybookProvider = (story) => html` ${story()} `; const dataTypeStoreProvider = (story) => html` - ${story()} + ${story()} `; const documentTypeStoreProvider = (story) => html` 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 bf9537a1a0..9fb5a26cb0 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts @@ -30,7 +30,8 @@ import { } from './documents/document-blueprints/document-blueprint.store'; import { UmbSectionStore, UMB_SECTION_STORE_CONTEXT_TOKEN } from './shared/components/section/section.store'; -import { UmbDataTypeStore, UMB_DATA_TYPE_STORE_CONTEXT_TOKEN } from './settings/data-types/data-type.store'; +import { UmbDataTypeDetailStore } from './settings/data-types/data-type.detail.store'; +import { UmbDataTypeTreeStore } from './settings/data-types/data-type.tree.store'; import { UmbLitElement } from '@umbraco-cms/element'; // Domains @@ -74,12 +75,13 @@ export class UmbBackofficeElement extends UmbLitElement { new UmbDocumentTreeStore(this); new UmbMediaDetailStore(this); new UmbMediaTreeStore(this); + new UmbDataTypeDetailStore(this); + new UmbDataTypeTreeStore(this); + new UmbUserStore(this); - this.provideContext(UMB_DATA_TYPE_STORE_CONTEXT_TOKEN, new UmbDataTypeStore(this)); this.provideContext(UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN, new UmbDocumentTypeStore(this)); this.provideContext(UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN, new UmbMediaTypeStore(this)); this.provideContext(UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN, new UmbMemberTypeStore(this)); - this.provideContext(UMB_USER_STORE_CONTEXT_TOKEN, new UmbUserStore(this)); this.provideContext(UMB_USER_GROUP_STORE_CONTEXT_TOKEN, new UmbUserGroupStore(this)); this.provideContext(UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN, new UmbMemberGroupStore(this)); this.provideContext(UMB_SECTION_STORE_CONTEXT_TOKEN, new UmbSectionStore()); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.store.ts deleted file mode 100644 index e6708b35cd..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.store.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { map, Observable } from 'rxjs'; -import type { DataTypeDetails } from '@umbraco-cms/models'; -import { DataTypeResource, FolderTreeItem } from '@umbraco-cms/backend-api'; -import { tryExecuteAndNotify } from '@umbraco-cms/resources'; -import { UmbContextToken } from '@umbraco-cms/context-api'; -import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; - -const isDataTypeDetails = (dataType: DataTypeDetails | FolderTreeItem): dataType is DataTypeDetails => { - return (dataType as DataTypeDetails).data !== undefined; -}; - -// TODO: can we make is easy to reuse store methods across different stores? - -export type UmbDataTypeStoreItemType = DataTypeDetails | FolderTreeItem; - -// TODO: research how we write names of global consts. -export const STORE_ALIAS = 'UmbDataTypeStore'; - -/** - * @export - * @class UmbDataTypesStore - * @extends {UmbDataStoreBase} - * @description - Data Store for Data Types - */ -export class UmbDataTypeStore extends UmbStoreBase { - - public readonly storeAlias = STORE_ALIAS; - - /** - * @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 UmbDataTypesStore - */ - getByKey(key: string): Observable { - // TODO: use backend cli when available. - fetch(`/umbraco/backoffice/data-type/details/${key}`) - .then((res) => res.json()) - .then((data) => { - this.updateItems(data); - }); - - return this.items.pipe( - map( - (dataTypes) => - (dataTypes.find((dataType) => dataType.key === key && isDataTypeDetails(dataType)) as DataTypeDetails) || null - ) - ); - } - - /** - * @description - Save a Data Type. - * @param {Array} dataTypes - * @memberof UmbDataTypesStore - * @return {*} {Promise} - */ - async save(dataTypes: Array): Promise { - // TODO: use backend cli when available. - try { - const res = await fetch('/umbraco/backoffice/data-type/save', { - method: 'POST', - body: JSON.stringify(dataTypes), - headers: { - 'Content-Type': 'application/json', - }, - }); - const json = await res.json(); - this.updateItems(json); - } catch (error) { - console.error('Save Data Type error', error); - } - } - - /** - * @description - Delete a Data Type. - * @param {string[]} keys - * @memberof UmbDataTypesStore - * @return {*} {Promise} - */ - async deleteItems(keys: string[]): Promise { - // TODO: use backend cli when available. - await fetch('/umbraco/backoffice/data-type/delete', { - method: 'POST', - body: JSON.stringify(keys), - headers: { - 'Content-Type': 'application/json', - }, - }); - - this.deleteItems(keys); - } - - /** - * @description - Get the root of the tree. - * @return {*} {Observable>} - * @memberof UmbDataTypesStore - */ - getTreeRoot(): Observable> { - tryExecuteAndNotify(this.host, DataTypeResource.getTreeDataTypeRoot({})).then(({ data }) => { - if (data) { - this.updateItems(data.items); - } - }); - - return this.items.pipe(map((items) => items.filter((item) => item.parentKey === null))); - } - - /** - * @description - Get the children of a tree item. - * @param {string} key - * @return {*} {Observable>} - * @memberof UmbDataTypesStore - */ - getTreeItemChildren(key: string): Observable> { - tryExecuteAndNotify( - this.host, - DataTypeResource.getTreeDataTypeChildren({ - parentKey: key, - }) - ).then(({ data }) => { - if (data) { - this.updateItems(data.items); - } - }); - - return this.items.pipe(map((items) => items.filter((item) => item.parentKey === key))); - } -} - -export const UMB_DATA_TYPE_STORE_CONTEXT_TOKEN = new UmbContextToken(STORE_ALIAS); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.tree.store.ts index 2e8b6aaa47..b5a5ea09c6 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.tree.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.tree.store.ts @@ -6,23 +6,23 @@ import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; -export const UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDocumentTreeStore'); +export const UMB_DATA_TYPE_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDataTypeTreeStore'); /** * @export - * @class UmbDocumentStore - * @extends {UmbStoreBase} + * @class UmbDataTypeTreeStore + * @extends {UmbStoreBase} * @description - Tree Data Store for Data Types */ -export class UmbDocumentTreeStore extends UmbStoreBase { +export class UmbDataTypeTreeStore extends UmbStoreBase { private _data = new UniqueArrayBehaviorSubject([], (x) => x.key); constructor(host: UmbControllerHostInterface) { - super(host, UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN.toString()); + super(host, UMB_DATA_TYPE_TREE_STORE_CONTEXT_TOKEN.toString()); } // TODO: How can we avoid having this in both stores? From 183f3f899226c825a024fba0c454dbd655d7f985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 21:26:26 +0100 Subject: [PATCH 14/35] use the ContentTreeItem type --- .../workspace/workspace-content/workspace-content.context.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts index 2fa4f7b445..a98da206ab 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts @@ -5,12 +5,12 @@ import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbContextConsumerController, UmbContextProviderController } from '@umbraco-cms/context-api'; import { UniqueBehaviorSubject, UmbObserverController, createObservablePart } from '@umbraco-cms/observable-api'; import { UmbContentStore } from '@umbraco-cms/stores/store'; -import type { ContentDetails } from '@umbraco-cms/models'; +import type { ContentTreeItem } from '@umbraco-cms/backend-api'; // TODO: Consider if its right to have this many class-inheritance of WorkspaceContext // TODO: Could we extract this code into a 'Manager' of its own, which will be instantiated by the concrete Workspace Context. This will be more transparent and 'reuseable' export abstract class UmbWorkspaceContentContext< - ContentTypeType extends ContentDetails = ContentDetails, + ContentTypeType extends ContentTreeItem = ContentTreeItem, StoreType extends UmbContentStore = UmbContentStore > { protected _host: UmbControllerHostInterface; From 0accc37df02a5c1d613f954b9a267fc0148723a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 21:29:07 +0100 Subject: [PATCH 15/35] rest of the store references --- .../delete/action-data-type-delete.element.ts | 7 ++++--- .../settings/data-types/tree/manifests.ts | 4 ++-- .../workspace/data-type-workspace.context.ts | 15 ++++++--------- .../content-property/content-property.element.ts | 7 ++++--- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/actions/delete/action-data-type-delete.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/actions/delete/action-data-type-delete.element.ts index 612ba06807..abc3105ea0 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/actions/delete/action-data-type-delete.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/actions/delete/action-data-type-delete.element.ts @@ -2,7 +2,8 @@ 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 { UmbDataTypeStore, UMB_DATA_TYPE_STORE_CONTEXT_TOKEN } from '../../../data-type.store'; +import { UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN } from '../../../data-type.detail.store'; +import type { UmbDataTypeDetailStore } from '../../../data-type.detail.store'; import UmbTreeItemActionElement from '../../../../../shared/components/tree/action/tree-item-action.element'; @customElement('umb-tree-action-data-type-delete') @@ -10,7 +11,7 @@ export default class UmbTreeActionDataTypeDeleteElement extends UmbTreeItemActio static styles = [UUITextStyles, css``]; private _modalService?: UmbModalService; - private _dataTypeStore?: UmbDataTypeStore; + private _dataTypeStore?: UmbDataTypeDetailStore; connectedCallback(): void { super.connectedCallback(); @@ -19,7 +20,7 @@ export default class UmbTreeActionDataTypeDeleteElement extends UmbTreeItemActio this._modalService = modalService; }); - this.consumeContext(UMB_DATA_TYPE_STORE_CONTEXT_TOKEN, (dataTypeStore) => { + this.consumeContext(UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN, (dataTypeStore) => { this._dataTypeStore = dataTypeStore; }); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/manifests.ts index 488465a362..a73a6c4236 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/manifests.ts @@ -1,4 +1,4 @@ -import { STORE_ALIAS } from '../data-type.store'; +import { UMB_DATA_TYPE_TREE_STORE_CONTEXT_TOKEN } from '../data-type.tree.store'; import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models'; const tree: ManifestTree = { @@ -7,7 +7,7 @@ const tree: ManifestTree = { name: 'Data Types Tree', weight: 100, meta: { - storeAlias: STORE_ALIAS, + storeAlias: UMB_DATA_TYPE_TREE_STORE_CONTEXT_TOKEN.toString(), }, }; 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 3d3a0eed5f..de3d6619fd 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,9 +1,6 @@ import { UmbWorkspaceContentContext } from '../../../shared/components/workspace/workspace-content/workspace-content.context'; -import { - UmbDataTypeStore, - UmbDataTypeStoreItemType, - UMB_DATA_TYPE_STORE_CONTEXT_TOKEN, -} from 'src/backoffice/settings/data-types/data-type.store'; +import { UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN} from '../../../settings/data-types/data-type.detail.store'; +import type { UmbDataTypeDetailStore} from '../../../settings/data-types/data-type.detail.store'; import type { DataTypeDetails, DataTypePropertyData } from '@umbraco-cms/models'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { appendToFrozenArray } from '@umbraco-cms/observable-api'; @@ -18,14 +15,14 @@ const DefaultDataTypeData = { propertyEditorModelAlias: '', propertyEditorUIAlias: '', data: [], -} as UmbDataTypeStoreItemType; +} as DataTypeDetails; export class UmbWorkspaceDataTypeContext extends UmbWorkspaceContentContext< - UmbDataTypeStoreItemType, - UmbDataTypeStore + DataTypeDetails, + UmbDataTypeDetailStore > { constructor(host: UmbControllerHostInterface) { - super(host, DefaultDataTypeData, UMB_DATA_TYPE_STORE_CONTEXT_TOKEN.toString(), 'dataType'); + super(host, DefaultDataTypeData, UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN.toString(), 'dataType'); } public setPropertyValue(alias: string, value: unknown) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/content-property/content-property.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/content-property/content-property.element.ts index 7ade9d09a1..23f0134e6e 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/content-property/content-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/content-property/content-property.element.ts @@ -3,7 +3,8 @@ import { css, html } from 'lit'; import { ifDefined } from 'lit-html/directives/if-defined.js'; import { customElement, property, state } from 'lit/decorators.js'; -import { UmbDataTypeStore, UMB_DATA_TYPE_STORE_CONTEXT_TOKEN } from '../../../settings/data-types/data-type.store'; +import { UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN } from '../../../settings/data-types/data-type.detail.store'; +import type { UmbDataTypeDetailStore } from '../../../settings/data-types/data-type.detail.store'; import type { ContentProperty, DataTypeDetails } from '@umbraco-cms/models'; import '../workspace-property/workspace-property.element'; @@ -44,13 +45,13 @@ export class UmbContentPropertyElement extends UmbLitElement { @state() private _dataTypeData?: any; - private _dataTypeStore?: UmbDataTypeStore; + private _dataTypeStore?: UmbDataTypeDetailStore; private _dataTypeObserver?: UmbObserverController; constructor() { super(); - this.consumeContext(UMB_DATA_TYPE_STORE_CONTEXT_TOKEN, (instance) => { + this.consumeContext(UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN, (instance) => { this._dataTypeStore = instance; this._observeDataType(this._property?.dataTypeKey); }); From 33140e6083c0a375419a8d71ffb6a78857087788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 21:40:54 +0100 Subject: [PATCH 16/35] media type store --- .../src/backoffice/backoffice.element.ts | 6 +- .../media-types/media-type.detail.store.ts | 59 ++++++++++++ .../media/media-types/media-type.store.ts | 93 ------------------- .../media-types/media-type.tree.store.ts | 70 ++++++++++++++ .../src/backoffice/users/users/user.store.ts | 15 ++- .../users/workspace/user-workspace.context.ts | 2 +- 6 files changed, 144 insertions(+), 101 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/media-type.detail.store.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/media-type.store.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/media-type.tree.store.ts 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 9fb5a26cb0..962dd1d6bc 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts @@ -16,7 +16,8 @@ import { UmbDocumentTypeStore, UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN, } from './documents/document-types/document-type.store'; -import { UmbMediaTypeStore, UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN } from './media/media-types/media-type.store'; +import { UmbMediaTypeDetailStore } from './media/media-types/media-type.detail.store'; +import { UmbMediaTypeTreeStore } from './media/media-types/media-type.tree.store'; import { UmbMemberTypeStore, UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN } from './members/member-types/member-type.store'; import { UmbDocumentDetailStore } from './documents/documents/document.detail.store'; import { UmbDocumentTreeStore } from './documents/documents/document.tree.store'; @@ -78,9 +79,10 @@ export class UmbBackofficeElement extends UmbLitElement { new UmbDataTypeDetailStore(this); new UmbDataTypeTreeStore(this); new UmbUserStore(this); + new UmbMediaTypeDetailStore(this); + new UmbMediaTypeTreeStore(this); this.provideContext(UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN, new UmbDocumentTypeStore(this)); - this.provideContext(UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN, new UmbMediaTypeStore(this)); this.provideContext(UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN, new UmbMemberTypeStore(this)); this.provideContext(UMB_USER_GROUP_STORE_CONTEXT_TOKEN, new UmbUserGroupStore(this)); this.provideContext(UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN, new UmbMemberGroupStore(this)); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/media-type.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/media-type.detail.store.ts new file mode 100644 index 0000000000..8bf1a63901 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/media-type.detail.store.ts @@ -0,0 +1,59 @@ +import type { DataTypeDetails } from '@umbraco-cms/models'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; + + +export const UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbMediaTypeDetailStore'); + + +/** + * @export + * @class UmbMediaTypeDetailStore + * @extends {UmbStoreBase} + * @description - Details Data Store for Media Types + */ +export class UmbMediaTypeDetailStore extends UmbStoreBase { + + + private _data = new UniqueArrayBehaviorSubject([], (x) => x.key); + + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT_TOKEN.toString()); + } + + /** + * @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 UmbMediaTypesStore + */ + 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} dataTypes + * @memberof UmbMediaTypesStore + * @return {*} {Promise} + */ + save(data: DataTypeDetails[]) { + return null as any; + } + + // TODO: How can we avoid having this in both stores? + /** + * @description - Delete a Media Type. + * @param {string[]} keys + * @memberof UmbMediaTypesStore + * @return {*} {Promise} + */ + async delete(keys: string[]) { + // TODO: use backend cli when available. + this._data.remove(keys); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/media-type.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/media-type.store.ts deleted file mode 100644 index 4a5e21868e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/media-type.store.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { map, Observable } from 'rxjs'; -import { UmbNodeStoreBase } from '../../../core/stores/store'; -import { MediaTypeResource, FolderTreeItem } from '@umbraco-cms/backend-api'; -import type { MediaTypeDetails } from '@umbraco-cms/models'; -import { tryExecuteAndNotify } from '@umbraco-cms/resources'; -import { UmbContextToken } from '@umbraco-cms/context-api'; - -export type UmbMediaTypeStoreItemType = MediaTypeDetails | FolderTreeItem; - -export const STORE_ALIAS = 'UmbMediaTypeStore'; - -/** - * @export - * @class UmbMediaTypeStore - * @extends {UmbDataStoreBase} - * @description - Data Store for Media Types - */ -export class UmbMediaTypeStore extends UmbNodeStoreBase { - public readonly storeAlias = STORE_ALIAS; - - /** - * @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 UmbMediaTypesStore - */ - getByKey(key: string): Observable { - // TODO: use backend cli when available. - /* - fetch(`/umbraco/backoffice/media-type/details/${key}`) - .then((res) => res.json()) - .then((data) => { - this.updateItems(data); - }); - - return this.items.pipe(map((mediaTypes) => mediaTypes.find((mediaType) => mediaType.key === key && isMediaTypeDetails(mediaType)) as UmbMediaTypeStoreItemType || null)); - */ - return null as any; - } - - /** - * @description - Save a Data Type. - * @param {Array} mediaTypes - * @memberof UmbMediaTypesStore - * @return {*} {Promise} - */ - async save(mediaTypes: Array): Promise { - // TODO: use backend cli when available. - /* - try { - const res = await fetch('/umbraco/backoffice/media-type/save', { - method: 'POST', - body: JSON.stringify(mediaTypes), - headers: { - 'Content-Type': 'application/json', - }, - }); - const json = await res.json(); - this.updateItems(json); - } catch (error) { - console.error('Save Data Type error', error); - } - */ - return null as any; - } - - getTreeRoot(): Observable> { - tryExecuteAndNotify(this.host, MediaTypeResource.getTreeMediaTypeRoot({})).then(({ data }) => { - if (data) { - this.updateItems(data.items); - } - }); - - return this.items.pipe(map((items) => items.filter((item) => item.parentKey === null))); - } - - getTreeItemChildren(key: string): Observable> { - tryExecuteAndNotify( - this.host, - MediaTypeResource.getTreeMediaTypeChildren({ - parentKey: key, - }) - ).then(({ data }) => { - if (data) { - this.updateItems(data.items); - } - }); - - return this.items.pipe(map((items) => items.filter((item) => item.parentKey === key))); - } -} - -export const UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN = new UmbContextToken(STORE_ALIAS); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/media-type.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/media-type.tree.store.ts new file mode 100644 index 0000000000..614ecda549 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/media-type.tree.store.ts @@ -0,0 +1,70 @@ +import { FolderTreeItem, MediaTypeResource } from '@umbraco-cms/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; + +export const UMB_DATA_TYPE_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDataTypeTreeStore'); + + +/** + * @export + * @class UmbDataTypeTreeStore + * @extends {UmbStoreBase} + * @description - Tree Data Store for Data Types + */ +export class UmbDataTypeTreeStore extends UmbStoreBase { + + + #data = new UniqueArrayBehaviorSubject([], (x) => x.key); + + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_DATA_TYPE_TREE_STORE_CONTEXT_TOKEN.toString()); + } + + + getTreeRoot() { + tryExecuteAndNotify(this._host, MediaTypeResource.getTreeMediaTypeRoot({})).then(({ data }) => { + if (data) { + this.#data.append(data.items); + } + }); + + return createObservablePart(this.#data, (items) => items.filter((item) => item.parentKey === null)); + } + + getTreeItemChildren(key: string){ + tryExecuteAndNotify( + this._host, + MediaTypeResource.getTreeMediaTypeChildren({ + parentKey: key, + }) + ).then(({ data }) => { + if (data) { + this.#data.append(data.items); + } + }); + + return createObservablePart(this.#data, (items) => items.filter((item) => item.parentKey === key)); + } + + getTreeItems(keys: Array) { + if (keys?.length > 0) { + tryExecuteAndNotify( + this._host, + MediaTypeResource.getTreeMediaTypeItem({ + 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 createObservablePart(this.#data, (items) => items.filter((item) => keys.includes(item.key ?? ''))); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/user.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/user.store.ts index fa95e5d89b..8283ac6337 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/user.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/user.store.ts @@ -3,10 +3,11 @@ import type { UserDetails } from '@umbraco-cms/models'; import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; import { UmbContextToken } from '@umbraco-cms/context-api'; import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; export type UmbUserStoreItemType = UserDetails; -export const STORE_ALIAS = 'UmbUserStore'; +export const UMB_USER_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbUserStore'); /** * @export @@ -14,15 +15,21 @@ export const STORE_ALIAS = 'UmbUserStore'; * @extends {UmbStoreBase} * @description - Data Store for Users */ - export class UmbUserStore extends UmbStoreBase { + #users = new UniqueArrayBehaviorSubject([], x => x.key); public users = this.#users.asObservable(); #totalUsers = new BehaviorSubject(0); public readonly totalUsers = this.#totalUsers.asObservable(); + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_USER_STORE_CONTEXT_TOKEN.toString()); + } + + getAll() { // TODO: use Fetcher API. // TODO: only fetch if the data type is not in the store? @@ -51,7 +58,7 @@ export class UmbUserStore extends UmbStoreBase { this.#users.appendOne(data); }); - return createObservablePart(this.#users, (users: Array) => users.find((user: UmbUserStoreItemType) => user.key === key) || null); + return createObservablePart(this.#users, (users: Array) => users.find((user: UmbUserStoreItemType) => user.key === key)); } @@ -281,5 +288,3 @@ export class UmbUserStore extends UmbStoreBase { // this.requestUpdate('users'); // } } - -export const UMB_USER_STORE_CONTEXT_TOKEN = new UmbContextToken(STORE_ALIAS); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts index 772f88ed9c..1393c00767 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts @@ -3,7 +3,7 @@ import { UmbUserStore, UmbUserStoreItemType, UMB_USER_STORE_CONTEXT_TOKEN, -} from 'src/backoffice/users/users/user.store'; +} from '../../users/user.store'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; const DefaultDataTypeData = { From 975e7453031aa01fbb6a7244447fe8b2aef3f946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 21:47:18 +0100 Subject: [PATCH 17/35] Document Type Store Refactoring --- .../.storybook/preview.js | 6 +- .../src/backoffice/backoffice.element.ts | 9 +- .../document-type.detail.store.ts | 98 +++++++++++++++++++ .../document-type.tree.store.ts | 96 ++++++++++++++++++ .../media-types/media-type.tree.store.ts | 10 +- 5 files changed, 206 insertions(+), 13 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.detail.store.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.tree.store.ts diff --git a/src/Umbraco.Web.UI.Client/.storybook/preview.js b/src/Umbraco.Web.UI.Client/.storybook/preview.js index b0a8b8e2d0..2af05724bc 100644 --- a/src/Umbraco.Web.UI.Client/.storybook/preview.js +++ b/src/Umbraco.Web.UI.Client/.storybook/preview.js @@ -12,8 +12,8 @@ import { initialize, mswDecorator } from 'msw-storybook-addon'; import { setCustomElements } from '@storybook/web-components'; import customElementManifests from '../custom-elements.json'; -import { UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN, UmbDataTypeStore } from '../src/backoffice/settings/data-types/data-type.detail.store'; -import { UmbDocumentTypeStore } from '../src/backoffice/documents/document-types/document-type.store'; +import { UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN, UmbDataTypeDetailStore } from '../src/backoffice/settings/data-types/data-type.detail.store'; +import { UMB_DOCUMENT_TYPE_DETAIL_STORE_CONTEXT_TOKEN, UmbDocumentTypeDetailStore } from '../src/backoffice/documents/document-types/document-type.detail.store'; import { UmbIconStore } from '../src/core/stores/icon/icon.store'; import { onUnhandledRequest } from '../src/core/mocks/browser'; import { handlers } from '../src/core/mocks/browser-handlers'; @@ -57,7 +57,7 @@ const dataTypeStoreProvider = (story) => html` `; const documentTypeStoreProvider = (story) => html` - ${story()} `; 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 962dd1d6bc..d606eb5a59 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts @@ -12,10 +12,8 @@ import { UMB_CURRENT_USER_HISTORY_STORE_CONTEXT_TOKEN, } from './users/current-user/current-user-history.store'; -import { - UmbDocumentTypeStore, - UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN, -} from './documents/document-types/document-type.store'; +import {UmbDocumentTypeDetailStore} from './documents/document-types/document-type.detail.store'; +import {UmbDocumentTypeTreeStore} from './documents/document-types/document-type.tree.store'; import { UmbMediaTypeDetailStore } from './media/media-types/media-type.detail.store'; import { UmbMediaTypeTreeStore } from './media/media-types/media-type.tree.store'; import { UmbMemberTypeStore, UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN } from './members/member-types/member-type.store'; @@ -81,8 +79,9 @@ export class UmbBackofficeElement extends UmbLitElement { new UmbUserStore(this); new UmbMediaTypeDetailStore(this); new UmbMediaTypeTreeStore(this); + new UmbMediaTypeDetailStore(this); + new UmbMediaTypeTreeStore(this); - this.provideContext(UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN, new UmbDocumentTypeStore(this)); this.provideContext(UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN, new UmbMemberTypeStore(this)); this.provideContext(UMB_USER_GROUP_STORE_CONTEXT_TOKEN, new UmbUserGroupStore(this)); this.provideContext(UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN, new UmbMemberGroupStore(this)); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.detail.store.ts new file mode 100644 index 0000000000..ef13935402 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.detail.store.ts @@ -0,0 +1,98 @@ +import type { DocumentTypeDetails } from '@umbraco-cms/models'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; + + +export const UMB_DOCUMENT_TYPE_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDocumentTypeDetailStore'); + + +/** + * @export + * @class UmbDocumentTypeDetailStore + * @extends {UmbStoreBase} + * @description - Details Data Store for Document Types + */ +export class UmbDocumentTypeDetailStore extends UmbStoreBase { + + + #data = new UniqueArrayBehaviorSubject([], (x) => x.key); + + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_DOCUMENT_TYPE_DETAIL_STORE_CONTEXT_TOKEN.toString()); + } + + /** + * @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 UmbDocumentTypesStore + */ + getByKey(key: string) { + // TODO: use backend cli when available. + fetch(`/umbraco/management/api/v1/document/document-type/${key}`) + .then((res) => res.json()) + .then((data) => { + this.#data.append(data); + }); + + return createObservablePart(this.#data, (documentTypes) => + documentTypes.find((documentType) => documentType.key === key) + ); + } + + // TODO: make sure UI somehow can follow the status of this action. + /** + * @description - Save a Data Type. + * @param {Array} documentTypes + * @memberof UmbDocumentTypesStore + * @return {*} {Promise} + */ + save(data: DocumentTypeDetails[]) { + // fetch from server and update store + // TODO: use Fetcher API. + let body: string; + + try { + body = JSON.stringify(data); + } catch (error) { + console.error(error); + return Promise.reject(); + } + + // TODO: use backend cli when available. + return fetch('/umbraco/management/api/v1/document-type/save', { + method: 'POST', + body: body, + headers: { + 'Content-Type': 'application/json', + }, + }) + .then((res) => res.json()) + .then((data: Array) => { + this.#data.append(data); + }); + } + + // TODO: How can we avoid having this in both stores? + /** + * @description - Delete a Data Type. + * @param {string[]} keys + * @memberof UmbDocumentTypesStore + * @return {*} {Promise} + */ + async delete(keys: string[]) { + // TODO: use backend cli when available. + await fetch('/umbraco/backoffice/document-type/delete', { + method: 'POST', + body: JSON.stringify(keys), + headers: { + 'Content-Type': 'application/json', + }, + }); + + this.#data.remove(keys); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.tree.store.ts new file mode 100644 index 0000000000..8b4561b61b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.tree.store.ts @@ -0,0 +1,96 @@ +import { DocumentTypeResource, DocumentTreeItem } from '@umbraco-cms/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; + + +export const UMB_DOCUMENT_TYPE_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDocumentTypeTreeStore'); + + +/** + * @export + * @class UmbDocumentTypeTreeStore + * @extends {UmbStoreBase} + * @description - Tree Data Store for Data Types + */ +export class UmbDocumentTypeTreeStore extends UmbStoreBase { + + + private _data = new UniqueArrayBehaviorSubject([], (x) => x.key); + + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_DOCUMENT_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 UmbDocumentTypesStore + * @return {*} {Promise} + */ + async delete(keys: string[]) { + // TODO: use backend cli when available. + await fetch('/umbraco/backoffice/document-type/delete', { + method: 'POST', + body: JSON.stringify(keys), + headers: { + 'Content-Type': 'application/json', + }, + }); + + this._data.remove(keys); + } + + getTreeRoot() { + tryExecuteAndNotify(this._host, DocumentTypeResource.getTreeDocumentTypeRoot({})).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: how do we handle trashed items? + // TODO: remove ignore when we know how to handle trashed items. + return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === null && !item.isTrashed)); + } + + getTreeItemChildren(key: string) { + tryExecuteAndNotify( + this._host, + DocumentTypeResource.getTreeDocumentTypeChildren({ + 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); + } + }); + + // TODO: how do we handle trashed items? + // TODO: remove ignore when we know how to handle trashed items. + return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === key && !item.isTrashed)); + } + + getTreeItems(keys: Array) { + if (keys?.length > 0) { + tryExecuteAndNotify( + this._host, + DocumentTypeResource.getTreeDocumentTypeItem({ + 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 createObservablePart(this._data, (items) => items.filter((item) => keys.includes(item.key ?? ''))); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/media-type.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/media-type.tree.store.ts index 614ecda549..f1f0911a6d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/media-type.tree.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/media-type.tree.store.ts @@ -1,20 +1,20 @@ -import { FolderTreeItem, MediaTypeResource } from '@umbraco-cms/backend-api'; +import { FolderTreeItem, MediaTypeResource } from '@umbraco-cms/backend-api'; import { tryExecuteAndNotify } from '@umbraco-cms/resources'; import { UmbContextToken } from '@umbraco-cms/context-api'; import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; -export const UMB_DATA_TYPE_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDataTypeTreeStore'); +export const UMB_DATA_TYPE_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbMediaTypeTreeStore'); /** * @export - * @class UmbDataTypeTreeStore + * @class UmbMediaTypeTreeStore * @extends {UmbStoreBase} - * @description - Tree Data Store for Data Types + * @description - Tree Data Store for Media Types */ -export class UmbDataTypeTreeStore extends UmbStoreBase { +export class UmbMediaTypeTreeStore extends UmbStoreBase { #data = new UniqueArrayBehaviorSubject([], (x) => x.key); From 884d8b158f9d9e58ffbc4af0f6663f3541c733d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 21:47:35 +0100 Subject: [PATCH 18/35] remove old store --- .../document-types/document-type.store.ts | 89 ------------------- 1 file changed, 89 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.store.ts diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.store.ts deleted file mode 100644 index f2d476129f..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.store.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { map, Observable } from 'rxjs'; -import { UmbDataStoreBase } from '../../../core/stores/store'; -import { DocumentTypeResource, DocumentTypeTreeItem } from '@umbraco-cms/backend-api'; -import type { DocumentTypeDetails } from '@umbraco-cms/models'; -import { tryExecuteAndNotify } from '@umbraco-cms/resources'; -import { UmbContextToken } from '@umbraco-cms/context-api'; - -export const isDocumentTypeDetails = ( - documentType: DocumentTypeDetails | DocumentTypeTreeItem -): documentType is DocumentTypeDetails => { - return (documentType as DocumentTypeDetails).properties !== undefined; -}; - -export type UmbDocumentTypeStoreItemType = DocumentTypeDetails | DocumentTypeTreeItem; - -export const STORE_ALIAS = 'UmbDocumentTypeStore'; - -/** - * @export - * @class UmbDocumentTypeStore - * @extends {UmbDataStoreBase} - * @description - Data Store for Document Types - */ -export class UmbDocumentTypeStore extends UmbDataStoreBase { - public readonly storeAlias = STORE_ALIAS; - - getByKey(key: string): Observable { - // TODO: use Fetcher API. - // TODO: only fetch if the data type is not in the store? - fetch(`/umbraco/backoffice/document-type/${key}`) - .then((res) => res.json()) - .then((data) => { - this.updateItems(data); - }); - - return this.items.pipe( - map( - (documentTypes) => - (documentTypes.find( - (documentType) => documentType.key === key && isDocumentTypeDetails(documentType) - ) as DocumentTypeDetails) || null - ) - ); - } - - async save(documentTypes: Array) { - // TODO: use Fetcher API. - try { - const res = await fetch('/umbraco/backoffice/document-type/save', { - method: 'POST', - body: JSON.stringify(documentTypes), - headers: { - 'Content-Type': 'application/json', - }, - }); - const json = await res.json(); - this.updateItems(json); - } catch (error) { - console.error('Save Document Type error', error); - } - } - - getTreeRoot(): Observable> { - tryExecuteAndNotify(this.host, DocumentTypeResource.getTreeDocumentTypeRoot({})).then(({ data }) => { - if (data) { - this.updateItems(data.items); - } - }); - - return this.items.pipe(map((items) => items.filter((item) => item.parentKey === null))); - } - - getTreeItemChildren(key: string): Observable> { - tryExecuteAndNotify( - this.host, - DocumentTypeResource.getTreeDocumentTypeChildren({ - parentKey: key, - }) - ).then(({ data }) => { - if (data) { - this.updateItems(data.items); - } - }); - - return this.items.pipe(map((items) => items.filter((item) => item.parentKey === key))); - } -} - -export const UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN = new UmbContextToken(STORE_ALIAS); From 979a9bb5968d459708d23101253b37b3e2f49e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 21:49:07 +0100 Subject: [PATCH 19/35] clean up usage of old document type store --- .../documents/document-types/tree/manifests.ts | 4 ++-- .../workspace/document-type-workspace.context.ts | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/tree/manifests.ts index 694de8a0bf..f0b8d07917 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/tree/manifests.ts @@ -1,4 +1,4 @@ -import { STORE_ALIAS } from '../document-type.store'; +import { UMB_DOCUMENT_TYPE_DETAIL_STORE_CONTEXT_TOKEN } from '../document-type.detail.store'; import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models'; const tree: ManifestTree = { @@ -6,7 +6,7 @@ const tree: ManifestTree = { alias: 'Umb.Tree.DocumentTypes', name: 'Document Types Tree', meta: { - storeAlias: STORE_ALIAS, + storeAlias: UMB_DOCUMENT_TYPE_DETAIL_STORE_CONTEXT_TOKEN.toString(), }, }; 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 a60d48833d..0214a2a2c4 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 @@ -1,10 +1,10 @@ import { UmbWorkspaceContentContext } from '../../../shared/components/workspace/workspace-content/workspace-content.context'; import { - UmbDocumentTypeStore, - UmbDocumentTypeStoreItemType, - UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN, -} from 'src/backoffice/documents/document-types/document-type.store'; + UmbDocumentTypeDetailStore, + UMB_DOCUMENT_TYPE_DETAIL_STORE_CONTEXT_TOKEN, +} from '../document-type.detail.store'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import type { DocumentTypeDetails } from '@umbraco-cms/models'; const DefaultDocumentTypeData = { key: '', @@ -15,14 +15,14 @@ const DefaultDocumentTypeData = { parentKey: '', alias: '', properties: [], -} as UmbDocumentTypeStoreItemType; +} as DocumentTypeDetails; export class UmbWorkspaceDocumentTypeContext extends UmbWorkspaceContentContext< - UmbDocumentTypeStoreItemType, - UmbDocumentTypeStore + DocumentTypeDetails, + UmbDocumentTypeDetailStore > { constructor(host: UmbControllerHostInterface) { - super(host, DefaultDocumentTypeData, UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN.toString(), 'documentType'); + super(host, DefaultDocumentTypeData, UMB_DOCUMENT_TYPE_DETAIL_STORE_CONTEXT_TOKEN.toString(), 'documentType'); } public setPropertyValue(alias: string, value: unknown) { From c9385f64276b9551b369a70235c7e43ed40f4dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 21:55:53 +0100 Subject: [PATCH 20/35] more corrections --- .../document-type.tree.store.ts | 6 +- ...space-view-document-type-design.element.ts | 4 +- .../member-types/member-type.detail.store.ts | 61 ++++++++++++ .../member-types/member-type.tree.store.ts | 96 +++++++++++++++++++ 4 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/member-type.detail.store.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/member-type.tree.store.ts diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.tree.store.ts index 8b4561b61b..4e56afb7d2 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.tree.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.tree.store.ts @@ -53,9 +53,8 @@ export class UmbDocumentTypeTreeStore extends UmbStoreBase { } }); - // TODO: how do we handle trashed items? // TODO: remove ignore when we know how to handle trashed items. - return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === null && !item.isTrashed)); + return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === null)); } getTreeItemChildren(key: string) { @@ -71,9 +70,8 @@ export class UmbDocumentTypeTreeStore extends UmbStoreBase { } }); - // TODO: how do we handle trashed items? // TODO: remove ignore when we know how to handle trashed items. - return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === key && !item.isTrashed)); + return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === key)); } getTreeItems(keys: Array) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/views/design/workspace-view-document-type-design.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/views/design/workspace-view-document-type-design.element.ts index 146230903f..1c845de142 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/views/design/workspace-view-document-type-design.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/views/design/workspace-view-document-type-design.element.ts @@ -3,15 +3,15 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, state } from 'lit/decorators.js'; import { distinctUntilChanged } from 'rxjs'; import { UmbWorkspaceDocumentTypeContext } from '../../document-type-workspace.context'; -import type { UmbDocumentTypeStoreItemType } from '../../../document-type.store'; import { UmbLitElement } from '@umbraco-cms/element'; +import type { DocumentTypeDetails } from '@umbraco-cms/models'; @customElement('umb-workspace-view-document-type-design') export class UmbWorkspaceViewDocumentTypeDesignElement extends UmbLitElement { static styles = [UUITextStyles, css``]; @state() - _documentType?: UmbDocumentTypeStoreItemType | null; + _documentType?: DocumentTypeDetails | null; private _workspaceContext?: UmbWorkspaceDocumentTypeContext; 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 new file mode 100644 index 0000000000..3ece2c275c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/member-type.detail.store.ts @@ -0,0 +1,61 @@ +import type { MemberTypeDetails } from '@umbraco-cms/models'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; +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 { + + + #data = new UniqueArrayBehaviorSubject([], (x) => x.key); + + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_MEMBER_TYPE_DETAIL_STORE_CONTEXT_TOKEN.toString()); + } + + /** + * @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 new file mode 100644 index 0000000000..3716b2600f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/member-type.tree.store.ts @@ -0,0 +1,96 @@ +import { EntityTreeItem, MemberTypeResource, } from '@umbraco-cms/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; +import { 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 Data Types + */ +export class UmbMemberTypeTreeStore extends UmbStoreBase { + + + // TODO: use the right type here: + #data = new UniqueArrayBehaviorSubject([], (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 createObservablePart(this.#data, (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 createObservablePart(this.#data, (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 createObservablePart(this.#data, (items) => items.filter((item) => keys.includes(item.key ?? ''))); + } +} From ddb6439c87f78bf1099422f02402ee47f2153eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 21:57:49 +0100 Subject: [PATCH 21/35] member type refactoring --- .../src/backoffice/backoffice.element.ts | 12 +++--- .../members/member-types/member-type.store.ts | 40 ------------------- .../member-types/member-type.tree.store.ts | 2 +- 3 files changed, 8 insertions(+), 46 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/member-type.store.ts 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 d606eb5a59..a52f8f5d52 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts @@ -4,7 +4,7 @@ import { css, html } from 'lit'; import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '../core/modal'; import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '../core/notification'; -import { UmbUserStore, UMB_USER_STORE_CONTEXT_TOKEN } from './users/users/user.store'; +import { UmbUserStore } from './users/users/user.store'; import { UmbUserGroupStore, UMB_USER_GROUP_STORE_CONTEXT_TOKEN } from './users/user-groups/user-group.store'; import { UmbCurrentUserStore, UMB_CURRENT_USER_STORE_CONTEXT_TOKEN } from './users/current-user/current-user.store'; import { @@ -16,11 +16,12 @@ import {UmbDocumentTypeDetailStore} from './documents/document-types/document-ty import {UmbDocumentTypeTreeStore} from './documents/document-types/document-type.tree.store'; import { UmbMediaTypeDetailStore } from './media/media-types/media-type.detail.store'; import { UmbMediaTypeTreeStore } from './media/media-types/media-type.tree.store'; -import { UmbMemberTypeStore, UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN } from './members/member-types/member-type.store'; import { UmbDocumentDetailStore } from './documents/documents/document.detail.store'; import { UmbDocumentTreeStore } from './documents/documents/document.tree.store'; import { UmbMediaDetailStore } from './media/media/media.detail.store'; import { UmbMediaTreeStore } from './media/media/media.tree.store'; +import { UmbMemberTypeDetailStore } from './members/member-types/member-type.detail.store'; +import { UmbMemberTypeTreeStore } from './members/member-types/member-type.tree.store'; import { UmbMemberGroupStore, UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN } from './members/member-groups/member-group.store'; import { UmbDictionaryStore, UMB_DICTIONARY_STORE_CONTEXT_TOKEN } from './translation/dictionary/dictionary.store'; import { @@ -79,10 +80,11 @@ export class UmbBackofficeElement extends UmbLitElement { new UmbUserStore(this); new UmbMediaTypeDetailStore(this); new UmbMediaTypeTreeStore(this); - new UmbMediaTypeDetailStore(this); - new UmbMediaTypeTreeStore(this); + new UmbDocumentTypeDetailStore(this); + new UmbDocumentTypeTreeStore(this); + new UmbMemberTypeDetailStore(this); + new UmbMemberTypeTreeStore(this); - this.provideContext(UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN, new UmbMemberTypeStore(this)); this.provideContext(UMB_USER_GROUP_STORE_CONTEXT_TOKEN, new UmbUserGroupStore(this)); this.provideContext(UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN, new UmbMemberGroupStore(this)); this.provideContext(UMB_SECTION_STORE_CONTEXT_TOKEN, new UmbSectionStore()); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/member-type.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/member-type.store.ts deleted file mode 100644 index 954d28e266..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-types/member-type.store.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { map, Observable } from 'rxjs'; -import { UmbDataStoreBase } from '../../../core/stores/store'; -import { MemberTypeResource, EntityTreeItem } from '@umbraco-cms/backend-api'; -import type { MemberTypeDetails } from '@umbraco-cms/models'; -import { tryExecuteAndNotify } from '@umbraco-cms/resources'; -import { UmbContextToken } from '@umbraco-cms/context-api'; - -export type UmbMemberTypeStoreItemType = MemberTypeDetails | EntityTreeItem; - -export const STORE_ALIAS = 'UmbMemberTypeStore'; - -/** - * @export - * @class UmbMemberTypeStore - * @extends {UmbDataStoreBase} - * @description - Data Store for Member Types - */ -export class UmbMemberTypeStore extends UmbDataStoreBase { - public readonly storeAlias = STORE_ALIAS; - - getByKey(key: string): Observable { - return null as any; - } - - async save(mediaTypes: Array): Promise { - return null as any; - } - - getTreeRoot(): Observable> { - tryExecuteAndNotify(this.host, MemberTypeResource.getTreeMemberTypeRoot({})).then(({ data }) => { - if (data) { - this.updateItems(data.items); - } - }); - - return this.items.pipe(map((items) => items.filter((item) => item.parentKey === null))); - } -} - -export const UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN = new UmbContextToken(STORE_ALIAS); 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 index 3716b2600f..4342809203 100644 --- 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 @@ -13,7 +13,7 @@ export const UMB_MEMBER_TYPE_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken Date: Mon, 23 Jan 2023 22:06:39 +0100 Subject: [PATCH 22/35] user-group.store --- .../input-user-group.element.ts | 4 +- .../src/backoffice/backoffice.element.ts | 4 +- .../users/user-groups/user-group.store.ts | 58 +++++++++---------- .../picker-layout-user-group.element.ts | 6 +- 4 files changed, 34 insertions(+), 38 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/auth/components/input-user-group/input-user-group.element.ts b/src/Umbraco.Web.UI.Client/src/auth/components/input-user-group/input-user-group.element.ts index 4b602520de..0f0eed532c 100644 --- a/src/Umbraco.Web.UI.Client/src/auth/components/input-user-group/input-user-group.element.ts +++ b/src/Umbraco.Web.UI.Client/src/auth/components/input-user-group/input-user-group.element.ts @@ -2,11 +2,11 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html, nothing } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbInputListBase } from '../../../backoffice/shared/components/input-list-base/input-list-base'; -import type { UserGroupEntity } from '@umbraco-cms/models'; import { UmbUserGroupStore, UMB_USER_GROUP_STORE_CONTEXT_TOKEN, -} from 'src/backoffice/users/user-groups/user-group.store'; +} from '../../../backoffice/users/user-groups/user-group.store'; +import type { UserGroupEntity } from '@umbraco-cms/models'; @customElement('umb-input-user-group') export class UmbInputPickerUserGroupElement extends UmbInputListBase { 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 a52f8f5d52..7bb106b9b8 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts @@ -5,7 +5,7 @@ import { css, html } from 'lit'; import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '../core/modal'; import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '../core/notification'; import { UmbUserStore } from './users/users/user.store'; -import { UmbUserGroupStore, UMB_USER_GROUP_STORE_CONTEXT_TOKEN } from './users/user-groups/user-group.store'; +import { UmbUserGroupStore } from './users/user-groups/user-group.store'; import { UmbCurrentUserStore, UMB_CURRENT_USER_STORE_CONTEXT_TOKEN } from './users/current-user/current-user.store'; import { UmbCurrentUserHistoryStore, @@ -84,8 +84,8 @@ export class UmbBackofficeElement extends UmbLitElement { new UmbDocumentTypeTreeStore(this); new UmbMemberTypeDetailStore(this); new UmbMemberTypeTreeStore(this); + new UmbUserGroupStore(this); - this.provideContext(UMB_USER_GROUP_STORE_CONTEXT_TOKEN, new UmbUserGroupStore(this)); this.provideContext(UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN, new UmbMemberGroupStore(this)); this.provideContext(UMB_SECTION_STORE_CONTEXT_TOKEN, new UmbSectionStore()); this.provideContext(UMB_CURRENT_USER_HISTORY_STORE_CONTEXT_TOKEN, new UmbCurrentUserHistoryStore()); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/user-group.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/user-group.store.ts index ebf7d6af87..1303dfc306 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/user-group.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/user-group.store.ts @@ -1,67 +1,67 @@ -import { map, Observable } from 'rxjs'; -import { UmbDataStoreBase } from '../../../core/stores/store'; -import type { UserGroupDetails, UserGroupEntity } from '@umbraco-cms/models'; +import type { UserGroupDetails } from '@umbraco-cms/models'; import { UmbContextToken } from '@umbraco-cms/context-api'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; // TODO: get rid of this type addition & { ... }: export type UmbUserGroupStoreItemType = UserGroupDetails & { users?: Array }; -export const STORE_ALIAS = 'UmbUserGroupStore'; +export const UMB_USER_GROUP_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbUserGroupStore'); /** * @export * @class UmbUserGroupStore - * @extends {UmbDataStoreBase} - * @description - Data Store for Users + * @extends {UmbStoreBase} + * @description - Data Store for User Groups */ -export class UmbUserGroupStore extends UmbDataStoreBase { - public readonly storeAlias = STORE_ALIAS; +export class UmbUserGroupStore extends UmbStoreBase { - getAll(): Observable> { + + #groups = new UniqueArrayBehaviorSubject([], x => x.key); + public groups = this.#groups.asObservable(); + + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_USER_GROUP_STORE_CONTEXT_TOKEN.toString()); + } + + getAll() { // TODO: use Fetcher API. // TODO: only fetch if the data type is not in the store? fetch(`/umbraco/backoffice/user-groups/list/items`) .then((res) => res.json()) .then((data) => { - this.updateItems(data.items); + this.#groups.append(data.items); }); - return this.items; + return this.groups; } - getByKey(key: string): Observable { + getByKey(key: string) { // TODO: use Fetcher API. // TODO: only fetch if the data type is not in the store? fetch(`/umbraco/backoffice/user-groups/details/${key}`) .then((res) => res.json()) .then((data) => { - this.updateItems([data]); + this.#groups.append([data]); }); - return this.items.pipe( - map( - (userGroups: Array) => - userGroups.find((userGroup: UmbUserGroupStoreItemType) => userGroup.key === key) || null - ) - ); + return createObservablePart(this.groups, (userGroups) => userGroups.find(userGroup => userGroup.key === key)); } - getByKeys(keys: Array): Observable> { + getByKeys(keys: Array) { const params = keys.map((key) => `key=${key}`).join('&'); fetch(`/umbraco/backoffice/user-groups/getByKeys?${params}`) .then((res) => res.json()) .then((data) => { - this.updateItems(data); + this.#groups.append(data); }); - return this.items.pipe( - map((items: Array) => - items.filter((node: UmbUserGroupStoreItemType) => keys.includes(node.key)) - ) - ); + return createObservablePart(this.groups, (items) => items.filter(node => keys.includes(node.key))); } - async save(userGroups: Array): Promise { + async save(userGroups: Array) { // TODO: use Fetcher API. // TODO: implement so user group store updates the @@ -80,11 +80,9 @@ export class UmbUserGroupStore extends UmbDataStoreBase(STORE_ALIAS); diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user-group/picker-layout-user-group.element.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user-group/picker-layout-user-group.element.ts index 1bfaf9fcbb..4ef941cf50 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user-group/picker-layout-user-group.element.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user-group/picker-layout-user-group.element.ts @@ -2,11 +2,9 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbModalLayoutPickerBase } from '../modal-layout-picker-base'; +import { UMB_USER_GROUP_STORE_CONTEXT_TOKEN } from '../../../../backoffice/users/user-groups/user-group.store'; +import type { UmbUserGroupStore } from '../../../../backoffice/users/user-groups/user-group.store'; import type { UserGroupDetails } from '@umbraco-cms/models'; -import { - UmbUserGroupStore, - UMB_USER_GROUP_STORE_CONTEXT_TOKEN, -} from 'src/backoffice/users/user-groups/user-group.store'; @customElement('umb-picker-layout-user-group') export class UmbPickerLayoutUserGroupElement extends UmbModalLayoutPickerBase { From 610546e14cbac2bef07894cb8cb888e49d5528dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 22:10:08 +0100 Subject: [PATCH 23/35] more updates --- .../src/backoffice/backoffice.element.ts | 2 +- .../member-group.details.store.ts | 34 ++++++++++++++++ .../member-groups/member-group.store.ts | 40 ------------------- .../members/member-groups/tree/manifests.ts | 2 +- .../users/user-groups/user-group.store.ts | 2 +- 5 files changed, 37 insertions(+), 43 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/member-group.details.store.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/member-group.store.ts 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 7bb106b9b8..4605883830 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts @@ -22,7 +22,7 @@ import { UmbMediaDetailStore } from './media/media/media.detail.store'; import { UmbMediaTreeStore } from './media/media/media.tree.store'; import { UmbMemberTypeDetailStore } from './members/member-types/member-type.detail.store'; import { UmbMemberTypeTreeStore } from './members/member-types/member-type.tree.store'; -import { UmbMemberGroupStore, UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN } from './members/member-groups/member-group.store'; +import { UmbMemberGroupStore, UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN } from './members/member-groups/member-group.details.store'; import { UmbDictionaryStore, UMB_DICTIONARY_STORE_CONTEXT_TOKEN } from './translation/dictionary/dictionary.store'; import { UmbDocumentBlueprintStore, diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/member-group.details.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/member-group.details.store.ts new file mode 100644 index 0000000000..dfe20a1e2d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/member-group.details.store.ts @@ -0,0 +1,34 @@ +import { Observable } from 'rxjs'; +import type { MemberGroupDetails } from '@umbraco-cms/models'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; + +export const UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbMemberGroupStore'); + +/** + * @export + * @class UmbMemberGroupStore + * @extends {UmbStoreBase} + * @description - Data Store for Member Groups + */ +export class UmbMemberGroupStore extends UmbStoreBase { + + + #groups = new UniqueArrayBehaviorSubject([], x => x.key); + public groups = this.#groups.asObservable(); + + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN.toString()); + } + + getByKey(key: string): Observable { + return null as any; + } + + async save(mediaTypes: Array): Promise { + return null as any; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/member-group.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/member-group.store.ts deleted file mode 100644 index 9be7cc8a67..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/member-group.store.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { map, Observable } from 'rxjs'; -import { UmbNodeStoreBase } from '../../../core/stores/store'; -import { EntityTreeItem, MemberGroupResource } from '@umbraco-cms/backend-api'; -import type { MemberGroupDetails } from '@umbraco-cms/models'; -import { tryExecuteAndNotify } from '@umbraco-cms/resources'; -import { UmbContextToken } from '@umbraco-cms/context-api'; - -export type UmbMemberGroupStoreItemType = MemberGroupDetails | EntityTreeItem; - -export const STORE_ALIAS = 'UmbMemberGroupStore'; - -/** - * @export - * @class UmbMemberGroupStore - * @extends {UmbDataStoreBase} - * @description - Data Store for Member Groups - */ -export class UmbMemberGroupStore extends UmbNodeStoreBase { - public readonly storeAlias = STORE_ALIAS; - - getByKey(key: string): Observable { - return null as any; - } - - async save(mediaTypes: Array): Promise { - return null as any; - } - - getTreeRoot(): Observable> { - tryExecuteAndNotify(this.host, MemberGroupResource.getTreeMemberGroupRoot({})).then(({ data }) => { - if (data) { - this.updateItems(data.items); - } - }); - - return this.items.pipe(map((items) => items.filter((item) => item.parentKey === null))); - } -} - -export const UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN = new UmbContextToken(STORE_ALIAS); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/tree/manifests.ts index 45b2878951..2e9d76d83e 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/tree/manifests.ts @@ -1,4 +1,4 @@ -import { STORE_ALIAS } from '../member-group.store'; +import { STORE_ALIAS } from '../member-group.details.store'; import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models'; const treeAlias = 'Umb.Tree.MemberGroups'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/user-group.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/user-group.store.ts index 1303dfc306..5713a9b087 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/user-group.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/user-group.store.ts @@ -7,7 +7,7 @@ import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; // TODO: get rid of this type addition & { ... }: export type UmbUserGroupStoreItemType = UserGroupDetails & { users?: Array }; -export const UMB_USER_GROUP_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbUserGroupStore'); +export const UMB_USER_GROUP_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbUserGroupStore'); /** * @export From 8420d3c9c7f2b295538ac0bb825996af49fd6662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 22:11:22 +0100 Subject: [PATCH 24/35] new UmbMemberGroupStore --- .../src/backoffice/backoffice.element.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 4605883830..f2494aa0c8 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts @@ -22,7 +22,7 @@ import { UmbMediaDetailStore } from './media/media/media.detail.store'; import { UmbMediaTreeStore } from './media/media/media.tree.store'; import { UmbMemberTypeDetailStore } from './members/member-types/member-type.detail.store'; import { UmbMemberTypeTreeStore } from './members/member-types/member-type.tree.store'; -import { UmbMemberGroupStore, UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN } from './members/member-groups/member-group.details.store'; +import { UmbMemberGroupStore } from './members/member-groups/member-group.details.store'; import { UmbDictionaryStore, UMB_DICTIONARY_STORE_CONTEXT_TOKEN } from './translation/dictionary/dictionary.store'; import { UmbDocumentBlueprintStore, @@ -85,8 +85,8 @@ export class UmbBackofficeElement extends UmbLitElement { new UmbMemberTypeDetailStore(this); new UmbMemberTypeTreeStore(this); new UmbUserGroupStore(this); + new UmbMemberGroupStore(this); - this.provideContext(UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN, new UmbMemberGroupStore(this)); this.provideContext(UMB_SECTION_STORE_CONTEXT_TOKEN, new UmbSectionStore()); this.provideContext(UMB_CURRENT_USER_HISTORY_STORE_CONTEXT_TOKEN, new UmbCurrentUserHistoryStore()); this.provideContext(UMB_DICTIONARY_STORE_CONTEXT_TOKEN, new UmbDictionaryStore(this)); From a2dfababd8ba7533747174511bf4ffc9d49d91dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 22:24:48 +0100 Subject: [PATCH 25/35] more stores --- .../src/backoffice/backoffice.element.ts | 15 +-- .../document-blueprint.detail.store.ts | 99 +++++++++++++++++ .../document-blueprint.tree.store.ts | 98 +++++++++++++++++ .../document-type.tree.store.ts | 16 +-- .../media/media/media.detail.store.ts | 10 +- .../media/media/media.tree.store.ts | 16 +-- .../data-types/data-type.detail.store.ts | 11 +- .../data-types/data-type.tree.store.ts | 16 +-- .../dictionary/dictionary.detail.store.ts | 100 ++++++++++++++++++ .../dictionary/dictionary.store.ts | 55 ---------- .../dictionary/dictionary.tree.store.ts | 96 +++++++++++++++++ .../translation/dictionary/tree/manifests.ts | 4 +- 12 files changed, 437 insertions(+), 99 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.detail.store.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.tree.store.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/dictionary.detail.store.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/dictionary.store.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/dictionary.tree.store.ts 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 f2494aa0c8..1a588bdf16 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts @@ -23,11 +23,10 @@ import { UmbMediaTreeStore } from './media/media/media.tree.store'; import { UmbMemberTypeDetailStore } from './members/member-types/member-type.detail.store'; import { UmbMemberTypeTreeStore } from './members/member-types/member-type.tree.store'; import { UmbMemberGroupStore } from './members/member-groups/member-group.details.store'; -import { UmbDictionaryStore, UMB_DICTIONARY_STORE_CONTEXT_TOKEN } from './translation/dictionary/dictionary.store'; -import { - UmbDocumentBlueprintStore, - UMB_DOCUMENT_BLUEPRINT_STORE_CONTEXT_TOKEN, -} from './documents/document-blueprints/document-blueprint.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'; +import { UmbDocumentBlueprintTreeStore } from './documents/document-blueprints/document-blueprint.tree.store'; import { UmbSectionStore, UMB_SECTION_STORE_CONTEXT_TOKEN } from './shared/components/section/section.store'; import { UmbDataTypeDetailStore } from './settings/data-types/data-type.detail.store'; @@ -86,11 +85,13 @@ export class UmbBackofficeElement extends UmbLitElement { new UmbMemberTypeTreeStore(this); new UmbUserGroupStore(this); new UmbMemberGroupStore(this); + new UmbDictionaryDetailStore(this); + new UmbDictionaryTreeStore(this); + new UmbDocumentBlueprintDetailStore(this); + new UmbDocumentBlueprintTreeStore(this); this.provideContext(UMB_SECTION_STORE_CONTEXT_TOKEN, new UmbSectionStore()); this.provideContext(UMB_CURRENT_USER_HISTORY_STORE_CONTEXT_TOKEN, new UmbCurrentUserHistoryStore()); - this.provideContext(UMB_DICTIONARY_STORE_CONTEXT_TOKEN, new UmbDictionaryStore(this)); - this.provideContext(UMB_DOCUMENT_BLUEPRINT_STORE_CONTEXT_TOKEN, new UmbDocumentBlueprintStore(this)); } render() { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.detail.store.ts new file mode 100644 index 0000000000..ae33ebb000 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.detail.store.ts @@ -0,0 +1,99 @@ +import type { DocumentBlueprintDetails } from '@umbraco-cms/models'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; + + +export const UMB_DocumentBlueprint_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDocumentBlueprintDetailStore'); + + +/** + * @export + * @class UmbDocumentBlueprintDetailStore + * @extends {UmbStoreBase} + * @description - Details Data Store for Document Blueprints + */ +export class UmbDocumentBlueprintDetailStore extends UmbStoreBase { + + + // TODO: use the right type: + #data = new UniqueArrayBehaviorSubject([], (x) => x.key); + + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_DocumentBlueprint_DETAIL_STORE_CONTEXT_TOKEN.toString()); + } + + /** + * @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 UmbDocumentBlueprintDetailStore + */ + getByKey(key: string) { + // TODO: use backend cli when available. + fetch(`/umbraco/management/api/v1/document/document-blueprint/${key}`) + .then((res) => res.json()) + .then((data) => { + this.#data.append(data); + }); + + return createObservablePart(this.#data, (documents) => + documents.find((document) => document.key === key) + ); + } + + // TODO: make sure UI somehow can follow the status of this action. + /** + * @description - Save a DocumentBlueprint. + * @param {Array} Dictionaries + * @memberof UmbDocumentBlueprintDetailStore + * @return {*} {Promise} + */ + save(data: DocumentBlueprintDetails[]) { + // fetch from server and update store + // TODO: use Fetcher API. + let body: string; + + try { + body = JSON.stringify(data); + } catch (error) { + console.error(error); + return Promise.reject(); + } + + // TODO: use backend cli when available. + return fetch('/umbraco/management/api/v1/document-blueprint/save', { + method: 'POST', + body: body, + headers: { + 'Content-Type': 'application/json', + }, + }) + .then((res) => res.json()) + .then((data: Array) => { + this.#data.append(data); + }); + } + + // TODO: How can we avoid having this in both stores? + /** + * @description - Delete a Data Type. + * @param {string[]} keys + * @memberof UmbDocumentBlueprintDetailStore + * @return {*} {Promise} + */ + async delete(keys: string[]) { + // TODO: use backend cli when available. + await fetch('/umbraco/backoffice/document-blueprint/delete', { + method: 'POST', + body: JSON.stringify(keys), + headers: { + 'Content-Type': 'application/json', + }, + }); + + this.#data.remove(keys); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.tree.store.ts new file mode 100644 index 0000000000..4170ff8749 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.tree.store.ts @@ -0,0 +1,98 @@ +import { DocumentBlueprintResource, DocumentTreeItem } from '@umbraco-cms/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; + + +export const UMB_DocumentBlueprint_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDocumentBlueprintTreeStore'); + + +/** + * @export + * @class UmbDocumentBlueprintTreeStore + * @extends {UmbStoreBase} + * @description - Tree Data Store for Document Blueprints + */ +export class UmbDocumentBlueprintTreeStore extends UmbStoreBase { + + + #data = new UniqueArrayBehaviorSubject([], (x) => x.key); + + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_DocumentBlueprint_TREE_STORE_CONTEXT_TOKEN.toString()); + } + + // TODO: How can we avoid having this in both stores? + /** + * @description - Delete a Document Blueprint Type. + * @param {string[]} keys + * @memberof UmbDocumentBlueprintsStore + * @return {*} {Promise} + */ + async delete(keys: string[]) { + // TODO: use backend cli when available. + await fetch('/umbraco/backoffice/data-type/delete', { + method: 'POST', + body: JSON.stringify(keys), + headers: { + 'Content-Type': 'application/json', + }, + }); + + this.#data.remove(keys); + } + + getTreeRoot() { + tryExecuteAndNotify(this._host, DocumentBlueprintResource.getTreeDocumentBlueprintRoot({})).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: how do we handle trashed items? + // TODO: remove ignore when we know how to handle trashed items. + return createObservablePart(this.#data, (items) => items.filter((item) => item.parentKey === null && !item.isTrashed)); + } + + getTreeItemChildren(key: string) { + /* + tryExecuteAndNotify( + this._host, + DocumentBlueprintResource.getTreeDocumentBlueprintChildren({ + 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); + } + }); + */ + + // TODO: how do we handle trashed items? + // TODO: remove ignore when we know how to handle trashed items. + return createObservablePart(this.#data, (items) => items.filter((item) => item.parentKey === key && !item.isTrashed)); + } + + getTreeItems(keys: Array) { + if (keys?.length > 0) { + tryExecuteAndNotify( + this._host, + DocumentBlueprintResource.getTreeDocumentBlueprintItem({ + 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 createObservablePart(this.#data, (items) => items.filter((item) => keys.includes(item.key ?? ''))); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.tree.store.ts index 4e56afb7d2..9be406d701 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.tree.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/document-type.tree.store.ts @@ -18,7 +18,7 @@ export const UMB_DOCUMENT_TYPE_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken([], (x) => x.key); + #data = new UniqueArrayBehaviorSubject([], (x) => x.key); constructor(host: UmbControllerHostInterface) { @@ -42,19 +42,19 @@ export class UmbDocumentTypeTreeStore extends UmbStoreBase { }, }); - this._data.remove(keys); + this.#data.remove(keys); } getTreeRoot() { tryExecuteAndNotify(this._host, DocumentTypeResource.getTreeDocumentTypeRoot({})).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); + this.#data.append(data.items); } }); // TODO: remove ignore when we know how to handle trashed items. - return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === null)); + return createObservablePart(this.#data, (items) => items.filter((item) => item.parentKey === null)); } getTreeItemChildren(key: string) { @@ -66,12 +66,12 @@ export class UmbDocumentTypeTreeStore extends UmbStoreBase { ).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); + this.#data.append(data.items); } }); // TODO: remove ignore when we know how to handle trashed items. - return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === key)); + return createObservablePart(this.#data, (items) => items.filter((item) => item.parentKey === key)); } getTreeItems(keys: Array) { @@ -84,11 +84,11 @@ export class UmbDocumentTypeTreeStore extends UmbStoreBase { ).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); + this.#data.append(data); } }); } - return createObservablePart(this._data, (items) => items.filter((item) => keys.includes(item.key ?? ''))); + return createObservablePart(this.#data, (items) => items.filter((item) => keys.includes(item.key ?? ''))); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.detail.store.ts index 5a9403138a..dd302bf1c2 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.detail.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.detail.store.ts @@ -18,7 +18,7 @@ export const UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken { - private _data = new UniqueArrayBehaviorSubject([], (x) => x.key); + #data = new UniqueArrayBehaviorSubject([], (x) => x.key); constructor(host: UmbControllerHostInterface) { @@ -30,10 +30,10 @@ export class UmbMediaDetailStore extends UmbStoreBase implements UmbContentStore fetch(`/umbraco/management/api/v1/media/details/${key}`) .then((res) => res.json()) .then((data) => { - this._data.append(data); + this.#data.append(data); }); - return createObservablePart(this._data, (documents) => + return createObservablePart(this.#data, (documents) => documents.find((document) => document.key === key) ); } @@ -61,7 +61,7 @@ export class UmbMediaDetailStore extends UmbStoreBase implements UmbContentStore }) .then((res) => res.json()) .then((data: Array) => { - this._data.append(data); + this.#data.append(data); }); } @@ -77,6 +77,6 @@ export class UmbMediaDetailStore extends UmbStoreBase implements UmbContentStore }, }); const data = await res.json(); - this._data.append(data); + this.#data.append(data); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.tree.store.ts index 36f1a3c9cd..06df2b1c92 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.tree.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/media.tree.store.ts @@ -22,7 +22,7 @@ export class UmbMediaTreeStore extends UmbStoreBase { - private _data = new UniqueArrayBehaviorSubject([], (x) => x.key); + #data = new UniqueArrayBehaviorSubject([], (x) => x.key); constructor(host: UmbControllerHostInterface) { @@ -41,20 +41,20 @@ export class UmbMediaTreeStore extends UmbStoreBase { }, }); const data = await res.json(); - this._data.append(data); + this.#data.append(data); } getTreeRoot(): Observable> { tryExecuteAndNotify(this._host, MediaResource.getTreeMediaRoot({})).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); + this.#data.append(data.items); } }); // TODO: how do we handle trashed items? // TODO: remove ignore when we know how to handle trashed items. - return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === null && !item.isTrashed)); + return createObservablePart(this.#data, (items) => items.filter((item) => item.parentKey === null && !item.isTrashed)); } getTreeItemChildren(key: string): Observable> { @@ -66,13 +66,13 @@ export class UmbMediaTreeStore extends UmbStoreBase { ).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); + this.#data.append(data.items); } }); // TODO: how do we handle trashed items? // TODO: remove ignore when we know how to handle trashed items. - return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === key && !item.isTrashed)); + return createObservablePart(this.#data, (items) => items.filter((item) => item.parentKey === key && !item.isTrashed)); } getTreeItems(keys: Array): Observable> { @@ -85,11 +85,11 @@ export class UmbMediaTreeStore extends UmbStoreBase { ).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); + this.#data.append(data); } }); } - return createObservablePart(this._data, (items) => items.filter((item) => keys.includes(item.key ?? ''))); + return createObservablePart(this.#data, (items) => items.filter((item) => keys.includes(item.key ?? ''))); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.detail.store.ts index 31b5035a34..e4531fb153 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.detail.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.detail.store.ts @@ -3,7 +3,6 @@ import { UmbContextToken } from '@umbraco-cms/context-api'; import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; -import { DataTypeResource } from '@umbraco-cms/backend-api'; export const UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDataTypeDetailStore'); @@ -18,7 +17,7 @@ export const UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken([], (x) => x.key); + #data = new UniqueArrayBehaviorSubject([], (x) => x.key); constructor(host: UmbControllerHostInterface) { @@ -36,10 +35,10 @@ export class UmbDataTypeDetailStore extends UmbStoreBase { fetch(`/umbraco/management/api/v1/document/data-type/${key}`) .then((res) => res.json()) .then((data) => { - this._data.append(data); + this.#data.append(data); }); - return createObservablePart(this._data, (documents) => + return createObservablePart(this.#data, (documents) => documents.find((document) => document.key === key) ); } @@ -73,7 +72,7 @@ export class UmbDataTypeDetailStore extends UmbStoreBase { }) .then((res) => res.json()) .then((data: Array) => { - this._data.append(data); + this.#data.append(data); }); } @@ -94,6 +93,6 @@ export class UmbDataTypeDetailStore extends UmbStoreBase { }, }); - this._data.remove(keys); + this.#data.remove(keys); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.tree.store.ts index b5a5ea09c6..6401a6ebb1 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.tree.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/data-type.tree.store.ts @@ -18,7 +18,7 @@ export const UMB_DATA_TYPE_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken([], (x) => x.key); + #data = new UniqueArrayBehaviorSubject([], (x) => x.key); constructor(host: UmbControllerHostInterface) { @@ -42,20 +42,20 @@ export class UmbDataTypeTreeStore extends UmbStoreBase { }, }); - this._data.remove(keys); + this.#data.remove(keys); } getTreeRoot() { tryExecuteAndNotify(this._host, DataTypeResource.getTreeDataTypeRoot({})).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); + this.#data.append(data.items); } }); // TODO: how do we handle trashed items? // TODO: remove ignore when we know how to handle trashed items. - return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === null && !item.isTrashed)); + return createObservablePart(this.#data, (items) => items.filter((item) => item.parentKey === null && !item.isTrashed)); } getTreeItemChildren(key: string) { @@ -67,13 +67,13 @@ export class UmbDataTypeTreeStore extends UmbStoreBase { ).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); + this.#data.append(data.items); } }); // TODO: how do we handle trashed items? // TODO: remove ignore when we know how to handle trashed items. - return createObservablePart(this._data, (items) => items.filter((item) => item.parentKey === key && !item.isTrashed)); + return createObservablePart(this.#data, (items) => items.filter((item) => item.parentKey === key && !item.isTrashed)); } getTreeItems(keys: Array) { @@ -86,11 +86,11 @@ export class UmbDataTypeTreeStore extends UmbStoreBase { ).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); + this.#data.append(data); } }); } - return createObservablePart(this._data, (items) => items.filter((item) => keys.includes(item.key ?? ''))); + return createObservablePart(this.#data, (items) => items.filter((item) => keys.includes(item.key ?? ''))); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/dictionary.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/dictionary.detail.store.ts new file mode 100644 index 0000000000..de60fd6bd9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/dictionary.detail.store.ts @@ -0,0 +1,100 @@ +import type { DictionaryDetails } from '@umbraco-cms/models'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { EntityTreeItem } from '@umbraco-cms/backend-api'; + + +export const UMB_DICTIONARY_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDictionaryDetailStore'); + + +/** + * @export + * @class UmbDictionaryDetailStore + * @extends {UmbStoreBase} + * @description - Details Data Store for Data Types + */ +export class UmbDictionaryDetailStore extends UmbStoreBase { + + + // TODO: use the right type: + #data = new UniqueArrayBehaviorSubject([], (x) => x.key); + + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_DICTIONARY_DETAIL_STORE_CONTEXT_TOKEN.toString()); + } + + /** + * @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 UmbDictionaryDetailStore + */ + getByKey(key: string) { + // TODO: use backend cli when available. + fetch(`/umbraco/management/api/v1/document/dictionary/${key}`) + .then((res) => res.json()) + .then((data) => { + this.#data.append(data); + }); + + return createObservablePart(this.#data, (documents) => + documents.find((document) => document.key === key) + ); + } + + // TODO: make sure UI somehow can follow the status of this action. + /** + * @description - Save a Dictionary. + * @param {Array} Dictionaries + * @memberof UmbDictionaryDetailStore + * @return {*} {Promise} + */ + save(data: DictionaryDetails[]) { + // fetch from server and update store + // TODO: use Fetcher API. + let body: string; + + try { + body = JSON.stringify(data); + } catch (error) { + console.error(error); + return Promise.reject(); + } + + // TODO: use backend cli when available. + return fetch('/umbraco/management/api/v1/dictionary/save', { + method: 'POST', + body: body, + headers: { + 'Content-Type': 'application/json', + }, + }) + .then((res) => res.json()) + .then((data: Array) => { + this.#data.append(data); + }); + } + + // TODO: How can we avoid having this in both stores? + /** + * @description - Delete a Data Type. + * @param {string[]} keys + * @memberof UmbDictionaryDetailStore + * @return {*} {Promise} + */ + async delete(keys: string[]) { + // TODO: use backend cli when available. + await fetch('/umbraco/backoffice/dictionary/delete', { + method: 'POST', + body: JSON.stringify(keys), + headers: { + 'Content-Type': 'application/json', + }, + }); + + this.#data.remove(keys); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/dictionary.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/dictionary.store.ts deleted file mode 100644 index 0fcf2c3fe5..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/dictionary.store.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { map, Observable } from 'rxjs'; -import { UmbDataStoreBase } from '../../../core/stores/store'; -import { DictionaryResource, EntityTreeItem } from '@umbraco-cms/backend-api'; -import { tryExecuteAndNotify } from '@umbraco-cms/resources'; -import { UmbContextToken } from '@umbraco-cms/context-api'; - -export const STORE_ALIAS = 'UmbDictionaryStore'; - -/** - * @export - * @class UmbDictionaryStore - * @extends {UmbDataStoreBase} - * @description - Data Store for Dictionary Items. - */ -export class UmbDictionaryStore extends UmbDataStoreBase { - public readonly storeAlias = STORE_ALIAS; - - /** - * @description - Get the root of the tree. - * @return {*} {Observable>} - * @memberof UmbDictionaryStore - */ - getTreeRoot(): Observable> { - tryExecuteAndNotify(this.host, DictionaryResource.getTreeDictionaryRoot({})).then(({ data }) => { - if (data) { - this.updateItems(data.items); - } - }); - - return this.items.pipe(map((items) => items.filter((item) => item.parentKey === null))); - } - - /** - * @description - Get the children of a tree item. - * @param {string} key - * @return {*} {Observable>} - * @memberof UmbDataTypesStore - */ - getTreeItemChildren(key: string): Observable> { - tryExecuteAndNotify( - this.host, - DictionaryResource.getTreeDictionaryChildren({ - parentKey: key, - }) - ).then(({ data }) => { - if (data) { - this.updateItems(data.items); - } - }); - - return this.items.pipe(map((items) => items.filter((item) => item.parentKey === key))); - } -} - -export const UMB_DICTIONARY_STORE_CONTEXT_TOKEN = new UmbContextToken(STORE_ALIAS); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/dictionary.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/dictionary.tree.store.ts new file mode 100644 index 0000000000..845c2076d4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/dictionary.tree.store.ts @@ -0,0 +1,96 @@ +import { DictionaryResource, DocumentTreeItem } from '@umbraco-cms/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { createObservablePart, UniqueArrayBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UmbStoreBase } from '@umbraco-cms/stores/store-base'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; + + +export const UMB_DICTIONARY_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDictionaryTreeStore'); + + +/** + * @export + * @class UmbDictionaryTreeStore + * @extends {UmbStoreBase} + * @description - Tree Data Store for Data Types + */ +export class UmbDictionaryTreeStore extends UmbStoreBase { + + + #data = new UniqueArrayBehaviorSubject([], (x) => x.key); + + + constructor(host: UmbControllerHostInterface) { + super(host, UMB_DICTIONARY_TREE_STORE_CONTEXT_TOKEN.toString()); + } + + // TODO: How can we avoid having this in both stores? + /** + * @description - Delete a Data Type. + * @param {string[]} keys + * @memberof UmbDictionarysStore + * @return {*} {Promise} + */ + async delete(keys: string[]) { + // TODO: use backend cli when available. + await fetch('/umbraco/backoffice/data-type/delete', { + method: 'POST', + body: JSON.stringify(keys), + headers: { + 'Content-Type': 'application/json', + }, + }); + + this.#data.remove(keys); + } + + getTreeRoot() { + tryExecuteAndNotify(this._host, DictionaryResource.getTreeDictionaryRoot({})).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: how do we handle trashed items? + // TODO: remove ignore when we know how to handle trashed items. + return createObservablePart(this.#data, (items) => items.filter((item) => item.parentKey === null && !item.isTrashed)); + } + + getTreeItemChildren(key: string) { + tryExecuteAndNotify( + this._host, + DictionaryResource.getTreeDictionaryChildren({ + 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); + } + }); + + // TODO: how do we handle trashed items? + // TODO: remove ignore when we know how to handle trashed items. + return createObservablePart(this.#data, (items) => items.filter((item) => item.parentKey === key && !item.isTrashed)); + } + + getTreeItems(keys: Array) { + if (keys?.length > 0) { + tryExecuteAndNotify( + this._host, + DictionaryResource.getTreeDictionaryItem({ + 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 createObservablePart(this.#data, (items) => items.filter((item) => keys.includes(item.key ?? ''))); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/tree/manifests.ts index f399178b43..8ba7f7d41e 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/tree/manifests.ts @@ -1,4 +1,4 @@ -import { STORE_ALIAS } from '../dictionary.store'; +import { UMB_DICTIONARY_TREE_STORE_CONTEXT_TOKEN } from '../dictionary.tree.store'; import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models'; const treeAlias = 'Umb.Tree.Dictionary'; @@ -8,7 +8,7 @@ const tree: ManifestTree = { alias: treeAlias, name: 'Dictionary Tree', meta: { - storeAlias: STORE_ALIAS, + storeAlias: UMB_DICTIONARY_TREE_STORE_CONTEXT_TOKEN.toString(), }, }; From 001a85de6eb6301744ae70e0edb313e724e49c99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 22:28:41 +0100 Subject: [PATCH 26/35] final elimination of UmbNodeStoreBase --- .../document-blueprint.store.ts | 44 ------------------- .../variant-selector.element.ts | 18 +++----- .../src/core/stores/store.ts | 2 +- 3 files changed, 8 insertions(+), 56 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.store.ts diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.store.ts deleted file mode 100644 index 66d2c6e907..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-blueprints/document-blueprint.store.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { map, Observable } from 'rxjs'; -import { UmbNodeStoreBase } from '../../../core/stores/store'; -import type { DocumentBlueprintDetails, DocumentDetails } from '@umbraco-cms/models'; -import { DocumentBlueprintTreeItem } from '@umbraco-cms/backend-api'; -import { UmbContextToken } from '@umbraco-cms/context-api'; - -export type UmbDocumentStoreItemType = DocumentBlueprintDetails | DocumentBlueprintTreeItem; - -export const STORE_ALIAS = 'UmbDocumentBlueprintStore'; - -const isDocumentBlueprintDetails = ( - documentBlueprint: DocumentBlueprintDetails | DocumentBlueprintTreeItem -): documentBlueprint is DocumentBlueprintDetails => { - return (documentBlueprint as DocumentBlueprintDetails).data !== undefined; -}; - -/** - * @export - * @class UmbDocumentStore - * @extends {UmbDocumentStoreBase} - * @description - Data Store for Documents - */ -export class UmbDocumentBlueprintStore extends UmbNodeStoreBase { - public readonly storeAlias = STORE_ALIAS; - - getByKey(key: string): Observable { - // TODO: implement call to end point - return this.items.pipe( - map( - (documentBlueprints) => - (documentBlueprints.find( - (documentBlueprint) => documentBlueprint.key === key && isDocumentBlueprintDetails(documentBlueprint) - ) as DocumentDetails) || null - ) - ); - } - - // TODO: implement call to end point - save(): any { - return; - } -} - -export const UMB_DOCUMENT_BLUEPRINT_STORE_CONTEXT_TOKEN = new UmbContextToken(STORE_ALIAS); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/variant-selector/variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/variant-selector/variant-selector.element.ts index dc605c8d92..e86656b85c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/variant-selector/variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/variant-selector/variant-selector.element.ts @@ -4,12 +4,8 @@ import { customElement, property, state } from 'lit/decorators.js'; import { UUIInputElement, UUIInputEvent } from '@umbraco-ui/uui'; import { distinctUntilChanged } from 'rxjs'; import type { UmbWorkspaceContentContext } from '../workspace/workspace-content/workspace-content.context'; -import type { DocumentDetails, MediaDetails } from '@umbraco-cms/models'; - -import type { UmbNodeStoreBase } from '@umbraco-cms/stores/store'; import { UmbLitElement } from '@umbraco-cms/element'; - -type ContentTypeTypes = DocumentDetails | MediaDetails; +import type { ContentTreeItem } from '@umbraco-cms/backend-api'; @customElement('umb-variant-selector') export class UmbVariantSelectorElement extends UmbLitElement { @@ -45,17 +41,17 @@ export class UmbVariantSelectorElement extends UmbLitElement { @property() alias!: string; - // TODO: use a more specific type here: + // TODO: use a more specific type here, something with variants. @state() - _content?: ContentTypeTypes; + _content?: ContentTreeItem; - private _workspaceContext?: UmbWorkspaceContentContext>; + private _workspaceContext?: UmbWorkspaceContentContext; constructor() { super(); // TODO: Figure out how to get the magic string for the workspace context. - this.consumeContext>>( + this.consumeContext( 'umbWorkspaceContext', (instance) => { this._workspaceContext = instance; @@ -99,7 +95,7 @@ export class UmbVariantSelectorElement extends UmbLitElement { ${ - this._content && this._content.variants?.length > 0 + this._content && (this._content as any).variants?.length > 0 ? html`
@@ -113,7 +109,7 @@ export class UmbVariantSelectorElement extends UmbLitElement { ${ - this._content && this._content.variants?.length > 0 + this._content && (this._content as any).variants?.length > 0 ? html`
diff --git a/src/Umbraco.Web.UI.Client/src/core/stores/store.ts b/src/Umbraco.Web.UI.Client/src/core/stores/store.ts index d4f461b3c8..754720bba6 100644 --- a/src/Umbraco.Web.UI.Client/src/core/stores/store.ts +++ b/src/Umbraco.Web.UI.Client/src/core/stores/store.ts @@ -31,7 +31,7 @@ export interface UmbContentStore extends UmbDataStore { * @description - Save data. * @param {object} data * @return {*} {(Promise)} - * @memberof UmbNodeStoreBase + * @memberof UmbContentStore */ save(data: T[]): Promise; } From df48635d3004b60d132afb639dc0a2719e789690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 22:29:49 +0100 Subject: [PATCH 27/35] usage of the alias from store --- .../src/backoffice/media/media-types/tree/manifests.ts | 4 ++-- .../src/backoffice/members/member-types/tree/manifests.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/tree/manifests.ts index 54a406aca4..d83d3c5967 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media-types/tree/manifests.ts @@ -1,4 +1,4 @@ -import { STORE_ALIAS } from '../media-type.store'; +import { UMB_DATA_TYPE_TREE_STORE_CONTEXT_TOKEN } from '../media-type.tree.store'; import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models'; const tree: ManifestTree = { @@ -6,7 +6,7 @@ const tree: ManifestTree = { alias: 'Umb.Tree.MediaTypes', name: 'Media Types Tree', meta: { - storeAlias: STORE_ALIAS, + storeAlias: UMB_DATA_TYPE_TREE_STORE_CONTEXT_TOKEN.toString(), }, }; 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 b6607ad3e4..fd0030ad39 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 { STORE_ALIAS } from '../member-type.store'; +import { UMB_MEMBER_TYPE_TREE_STORE_CONTEXT_TOKEN } from '../member-type.tree.store'; 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: STORE_ALIAS, + storeAlias: UMB_MEMBER_TYPE_TREE_STORE_CONTEXT_TOKEN.toString(), }, }; From 37e39c968936267daacb38bd01c2cf4d3c73f5fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 22:31:12 +0100 Subject: [PATCH 28/35] import fix --- .../src/auth/components/input-user/input-user.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/auth/components/input-user/input-user.element.ts b/src/Umbraco.Web.UI.Client/src/auth/components/input-user/input-user.element.ts index 552be4346b..d1cde6e11c 100644 --- a/src/Umbraco.Web.UI.Client/src/auth/components/input-user/input-user.element.ts +++ b/src/Umbraco.Web.UI.Client/src/auth/components/input-user/input-user.element.ts @@ -2,8 +2,8 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html, nothing, PropertyValueMap } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbInputListBase } from '../../../backoffice/shared/components/input-list-base/input-list-base'; +import { UmbUserStore, UMB_USER_STORE_CONTEXT_TOKEN } from '../../../backoffice/users/users/user.store'; import type { UserEntity } from '@umbraco-cms/models'; -import { UmbUserStore, UMB_USER_STORE_CONTEXT_TOKEN } from 'src/backoffice/users/users/user.store'; @customElement('umb-input-user') export class UmbPickerUserElement extends UmbInputListBase { From ad463ddc0dba446a0a7e862f0d379f2e2e83ea6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 22:33:58 +0100 Subject: [PATCH 29/35] use UmbTreeStore --- .../src/backoffice/documents/documents/tree/manifests.ts | 4 ++-- .../documents/workspace/document-workspace.context.ts | 4 ++-- .../src/backoffice/members/member-groups/tree/manifests.ts | 4 ++-- .../tree/actions/delete/action-data-type-delete.element.ts | 2 +- .../src/backoffice/shared/collection/collection.context.ts | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/tree/manifests.ts index 09a36620ae..6021b6d817 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/tree/manifests.ts @@ -1,4 +1,4 @@ -import { STORE_ALIAS } from '../document.detail.store'; +import { UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN } from '../document.detail.store'; import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models'; const treeAlias = 'Umb.Tree.Documents'; @@ -8,7 +8,7 @@ const tree: ManifestTree = { alias: treeAlias, name: 'Documents Tree', meta: { - storeAlias: STORE_ALIAS, + storeAlias: UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN.toString(), }, }; 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 edb54a7eb6..4f60b6d583 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 @@ -1,5 +1,5 @@ import { UmbWorkspaceContentContext } from '../../../shared/components/workspace/workspace-content/workspace-content.context'; -import { DOCUMENT_DETAIL_STORE_ALIAS } from '../document.detail.store'; +import { UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN } from '../document.detail.store'; import type { UmbDocumentDetailStore } from '../document.detail.store'; import type { UmbControllerHostInterface } from '@umbraco-cms/controller'; import type { DocumentDetails } from '@umbraco-cms/models'; @@ -36,7 +36,7 @@ const DefaultDocumentData = { export class UmbWorkspaceDocumentContext extends UmbWorkspaceContentContext { constructor(host: UmbControllerHostInterface) { - super(host, DefaultDocumentData, DOCUMENT_DETAIL_STORE_ALIAS, 'document'); + super(host, DefaultDocumentData, UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN.toString(), 'document'); } public setPropertyValue(alias: string, value: unknown) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/tree/manifests.ts index 2e9d76d83e..8f65257d9a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/tree/manifests.ts @@ -1,4 +1,4 @@ -import { STORE_ALIAS } from '../member-group.details.store'; +import { UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN } from '../member-group.details.store'; import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models'; const treeAlias = 'Umb.Tree.MemberGroups'; @@ -8,7 +8,7 @@ const tree: ManifestTree = { alias: treeAlias, name: 'Member Groups Tree', meta: { - storeAlias: STORE_ALIAS, + storeAlias: UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN.toString(), }, }; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/actions/delete/action-data-type-delete.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/actions/delete/action-data-type-delete.element.ts index abc3105ea0..3d80fb6313 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/actions/delete/action-data-type-delete.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/tree/actions/delete/action-data-type-delete.element.ts @@ -35,7 +35,7 @@ export default class UmbTreeActionDataTypeDeleteElement extends UmbTreeItemActio modalHandler?.onClose().then(({ confirmed }: any) => { if (confirmed && this._treeContextMenuService && this._dataTypeStore && this._activeTreeItem) { - this._dataTypeStore?.deleteItems([this._activeTreeItem.key]); + this._dataTypeStore?.delete([this._activeTreeItem.key]); this._treeContextMenuService.close(); } }); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts index 8a23c626b1..3bdcfe9ebc 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts @@ -1,11 +1,11 @@ import { ContentTreeItem } from '@umbraco-cms/backend-api'; -import { UmbTreeDataStore } from '@umbraco-cms/stores/store'; +import { UmbTreeStore } from '@umbraco-cms/stores/store'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbContextToken, UmbContextConsumerController } from '@umbraco-cms/context-api'; import { UniqueBehaviorSubject, UmbObserverController } from '@umbraco-cms/observable-api'; export class UmbCollectionContext< DataType extends ContentTreeItem, - StoreType extends UmbTreeDataStore = UmbTreeDataStore + StoreType extends UmbTreeStore = UmbTreeStore > { private _host: UmbControllerHostInterface; private _entityKey: string | null; From 7f325b97d8a47eac63c991f0a0b2811d04346b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 22:37:11 +0100 Subject: [PATCH 30/35] use right types --- .../input-document-picker.element.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-document-picker/input-document-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-document-picker/input-document-picker.element.ts index 5c1e5ddb40..080c1287b3 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-document-picker/input-document-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-document-picker/input-document-picker.element.ts @@ -3,12 +3,12 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, property, state } from 'lit/decorators.js'; import { ifDefined } from 'lit-html/directives/if-defined.js'; import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; -import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from 'src/core/modal'; -import type { FolderTreeItem } from '@umbraco-cms/backend-api'; +import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '../../../../core/modal'; +import { UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN } from '../../../../backoffice/documents/documents/document.tree.store'; +import type { UmbDocumentTreeStore } from '../../../../backoffice/documents/documents/document.tree.store'; import { UmbLitElement } from '@umbraco-cms/element'; +import type { DocumentTreeItem, FolderTreeItem } from '@umbraco-cms/backend-api'; import type { UmbObserverController } from '@umbraco-cms/observable-api'; -import type { UmbDocumentDetailStore } from 'src/backoffice/documents/documents/document.detail.store'; -import { UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN } from 'src/backoffice/documents/documents/document.detail.store'; @customElement('umb-input-document-picker') export class UmbInputDocumentPickerElement extends FormControlMixin(UmbLitElement) { @@ -75,10 +75,10 @@ export class UmbInputDocumentPickerElement extends FormControlMixin(UmbLitElemen } @state() - private _items?: Array; + private _items?: Array; private _modalService?: UmbModalService; - private _documentStore?: UmbDocumentDetailStore; + private _documentStore?: UmbDocumentTreeStore; private _pickedItemsObserver?: UmbObserverController; constructor() { @@ -95,7 +95,7 @@ export class UmbInputDocumentPickerElement extends FormControlMixin(UmbLitElemen () => !!this.max && this._selectedKeys.length > this.max ); - this.consumeContext(UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN, (instance) => { + this.consumeContext(UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN, (instance) => { this._documentStore = instance; this._observePickedDocuments(); }); From 40e6ad99c80cbaa43348625bef01f4867593209e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 22:37:24 +0100 Subject: [PATCH 31/35] update test --- .../core/observable-api/unique-array-behavior-subject.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-array-behavior-subject.test.ts b/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-array-behavior-subject.test.ts index ab98b2ac50..8ab7688698 100644 --- a/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-array-behavior-subject.test.ts +++ b/src/Umbraco.Web.UI.Client/src/core/observable-api/unique-array-behavior-subject.test.ts @@ -1,6 +1,6 @@ import { expect } from '@open-wc/testing'; -import { createObservablePart } from '@umbraco-cms/observable-api'; import { UniqueArrayBehaviorSubject } from './unique-array-behavior-subject'; +import { createObservablePart } from '@umbraco-cms/observable-api'; describe('UniqueArrayBehaviorSubject', () => { @@ -16,7 +16,7 @@ describe('UniqueArrayBehaviorSubject', () => { {key: '2', another: 'myValue2'}, {key: '3', another: 'myValue3'} ]; - subject = new UniqueArrayBehaviorSubject(initialData, (a, b) => a.key === b.key); + subject = new UniqueArrayBehaviorSubject(initialData, x => x.key); }); From 30b1bb4abc295e52977039ca44aa04325e2cc64a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 23 Jan 2023 22:44:37 +0100 Subject: [PATCH 32/35] correct type usages --- .../dashboards/dashboard-collection.element.ts | 10 ++++++---- .../collection/workspace-view-collection.element.ts | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/dashboards/dashboard-collection.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/dashboards/dashboard-collection.element.ts index 22426767bd..8edcf49ed8 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/dashboards/dashboard-collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/dashboards/dashboard-collection.element.ts @@ -3,12 +3,13 @@ import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import '../collection.element'; import { ifDefined } from 'lit-html/directives/if-defined.js'; -import { UmbMediaDetailStore } from 'src/backoffice/media/media/media.detail.store'; +import { UmbMediaTreeStore } from '../../../media/media/media.tree.store'; import { UmbCollectionContext, UMB_COLLECTION_CONTEXT_TOKEN, -} from 'src/backoffice/shared/collection/collection.context'; -import type { ManifestDashboardCollection, MediaDetails } from '@umbraco-cms/models'; +} from '../../../shared/collection/collection.context'; +import type { ManifestDashboardCollection } from '@umbraco-cms/models'; +import type { FolderTreeItem } from '@umbraco-cms/backend-api'; import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-dashboard-collection') @@ -26,7 +27,8 @@ export class UmbDashboardCollectionElement extends UmbLitElement { `, ]; - private _collectionContext?: UmbCollectionContext; + // TODO: Use the right type here: + private _collectionContext?: UmbCollectionContext; public manifest!: ManifestDashboardCollection; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/collection/workspace-view-collection.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/collection/workspace-view-collection.element.ts index 655b308977..255b7eb11b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/collection/workspace-view-collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/collection/workspace-view-collection.element.ts @@ -3,16 +3,16 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement } from 'lit/decorators.js'; import { ifDefined } from 'lit-html/directives/if-defined.js'; import { UmbWorkspaceContentContext } from '../../workspace-content.context'; +import { UmbMediaTreeStore } from '../../../../../../media/media/media.tree.store'; import { UmbCollectionContext, UMB_COLLECTION_CONTEXT_TOKEN, -} from 'src/backoffice/shared/collection/collection.context'; -import { UmbMediaDetailStore } from 'src/backoffice/media/media/media.detail.store'; +} from '../../../../../../shared/collection/collection.context'; import '../../../../../../shared/components/content-property/content-property.element'; import '../../../../../../shared/collection/dashboards/dashboard-collection.element'; import { UmbLitElement } from '@umbraco-cms/element'; -import type { MediaDetails } from '@umbraco-cms/models'; +import { FolderTreeItem } from '@umbraco-cms/backend-api'; @customElement('umb-workspace-view-collection') export class UmbWorkspaceViewCollectionElement extends UmbLitElement { @@ -28,7 +28,7 @@ export class UmbWorkspaceViewCollectionElement extends UmbLitElement { private _workspaceContext?: UmbWorkspaceContentContext; - private _collectionContext?: UmbCollectionContext; + private _collectionContext?: UmbCollectionContext; constructor() { super(); From d317d48c6e7ea3d7d4909ad3e271cea49b5347d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 24 Jan 2023 09:53:49 +0100 Subject: [PATCH 33/35] add type to content-property --- .../components/content-property/content-property.element.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/content-property/content-property.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/content-property/content-property.element.ts index 23f0134e6e..744d5febe0 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/content-property/content-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/content-property/content-property.element.ts @@ -5,7 +5,7 @@ import { customElement, property, state } from 'lit/decorators.js'; import { UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN } from '../../../settings/data-types/data-type.detail.store'; import type { UmbDataTypeDetailStore } from '../../../settings/data-types/data-type.detail.store'; -import type { ContentProperty, DataTypeDetails } from '@umbraco-cms/models'; +import type { ContentProperty, DataTypeDetails, DataTypePropertyData } from '@umbraco-cms/models'; import '../workspace-property/workspace-property.element'; import { UmbLitElement } from '@umbraco-cms/element'; @@ -43,7 +43,7 @@ export class UmbContentPropertyElement extends UmbLitElement { private _propertyEditorUIAlias?: string; @state() - private _dataTypeData?: any; + private _dataTypeData: DataTypePropertyData[] = []; private _dataTypeStore?: UmbDataTypeDetailStore; private _dataTypeObserver?: UmbObserverController; @@ -63,7 +63,7 @@ export class UmbContentPropertyElement extends UmbLitElement { this._dataTypeObserver?.destroy(); if (dataTypeKey) { this._dataTypeObserver = this.observe(this._dataTypeStore.getByKey(dataTypeKey), (dataType) => { - this._dataTypeData = dataType?.data; + this._dataTypeData = dataType?.data || []; this._propertyEditorUIAlias = dataType?.propertyEditorUIAlias || undefined; }); } From 0d72ae0120df77a66d82f1c680b0e86cc73143b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 24 Jan 2023 11:12:25 +0100 Subject: [PATCH 34/35] use UniqueObjectBehaviorSubject --- .../backoffice/shared/components/section/section.context.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.context.ts index a599227afe..e5b4af939d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.context.ts @@ -1,6 +1,6 @@ import { BehaviorSubject } from 'rxjs'; import type { Entity, ManifestSection, ManifestSectionView, ManifestTree } from '@umbraco-cms/models'; -import { UniqueBehaviorSubject } from '@umbraco-cms/observable-api'; +import { UniqueObjectBehaviorSubject } from '@umbraco-cms/observable-api'; import { UmbContextToken } from '@umbraco-cms/context-api'; export class UmbSectionContext { @@ -12,7 +12,7 @@ export class UmbSectionContext { public readonly activeTree = this._activeTree.asObservable(); // TODO: what is the best context to put this in? - private _activeTreeItem = new UniqueBehaviorSubject(undefined); + private _activeTreeItem = new UniqueObjectBehaviorSubject(undefined); public readonly activeTreeItem = this._activeTreeItem.asObservable(); // TODO: what is the best context to put this in? @@ -20,7 +20,7 @@ export class UmbSectionContext { public readonly activeView = this._activeView.asObservable(); constructor(sectionManifest: ManifestSection) { - this.#manifest = new UniqueBehaviorSubject(sectionManifest); + this.#manifest = new BehaviorSubject(sectionManifest); this.manifest = this.#manifest.asObservable(); } From 719d88d6a7ab3b6e16d4c7a25593bcef0883c463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 24 Jan 2023 11:16:42 +0100 Subject: [PATCH 35/35] move append-to-frozen-array method --- .../core => libs}/observable-api/append-to-frozen-array.method.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Umbraco.Web.UI.Client/{src/core => libs}/observable-api/append-to-frozen-array.method.ts (100%) diff --git a/src/Umbraco.Web.UI.Client/src/core/observable-api/append-to-frozen-array.method.ts b/src/Umbraco.Web.UI.Client/libs/observable-api/append-to-frozen-array.method.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/core/observable-api/append-to-frozen-array.method.ts rename to src/Umbraco.Web.UI.Client/libs/observable-api/append-to-frozen-array.method.ts