diff --git a/src/Umbraco.Web.UI.Client/.github/CONTRIBUTING.md b/src/Umbraco.Web.UI.Client/.github/CONTRIBUTING.md index 24741b1823..e38509ec18 100644 --- a/src/Umbraco.Web.UI.Client/.github/CONTRIBUTING.md +++ b/src/Umbraco.Web.UI.Client/.github/CONTRIBUTING.md @@ -13,15 +13,12 @@ Here is the LIT documentation and playground: [https://lit.dev](https://lit.dev) ### What is the process of contribution? -* Umbraco HQ owns the Management API on the backend -* Features can be worked on in the frontend when there is an API, or otherwise, if no API is required -* A contribution should be made in a fork of the backoffice repository -* Once a contribution is ready, a pull request should be made towards the backoffice repository, where HQ will assign a reviewer -* A pull request should always indicate what part of a feature it tries to solve, i.e. which labels can be removed from the issue on the projects board - -### How to work with Github Projects - -TBD +- Read the [README](README.md) to learn how to get the project up and running +- Find an issue marked as [community/up-for-grabs](https://github.com/umbraco/Umbraco.CMS.Backoffice/issues?q=is%3Aissue+is%3Aopen+label%3Acommunity%2Fup-for-grabs) - note that some are also marked [good first issue](https://github.com/umbraco/Umbraco.CMS.Backoffice/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) which indicates they are simple to get started on +- Umbraco HQ owns the Management API on the backend, so features can be worked on in the frontend only when there is an API, or otherwise, if no API is required +- A contribution should be made in a fork of the repository +- Once a contribution is ready, a pull request should be made towards this repository and HQ will assign a reviewer +- A pull request should always indicate what part of a feature it tries to solve, i.e. does it close the targeted issue (if any) or does the developer expect Umbraco HQ to take over ## Contributing in general terms diff --git a/src/Umbraco.Web.UI.Client/.github/ISSUE_TEMPLATE/config.yml b/src/Umbraco.Web.UI.Client/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..999d82b31b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: 💡 Features and ideas + url: https://github.com/umbraco/Umbraco.CMS.Backoffice/discussions/new?category=ideas + about: Start a new discussion when you have ideas or feature requests, eventually discussions can turn into plans. + - name: ❓ Support Question + url: https://our.umbraco.com + about: This issue tracker is NOT meant for support questions. If you have a question, please join us on the discussions board or Discord. diff --git a/src/Umbraco.Web.UI.Client/.github/ISSUE_TEMPLATE/feature_request.md b/src/Umbraco.Web.UI.Client/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..bbcbbe7d61 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/src/Umbraco.Web.UI.Client/.github/README.md b/src/Umbraco.Web.UI.Client/.github/README.md index 68201b62c3..9cc0f21d62 100644 --- a/src/Umbraco.Web.UI.Client/.github/README.md +++ b/src/Umbraco.Web.UI.Client/.github/README.md @@ -39,3 +39,7 @@ See the Main branch in action here as an [Azure Static Web App](https://ashy-bay ### Storybook Storybook is also being built and deployed automatically on the Main branch, including a preview URL on each pull request. See it in action on this [Azure Static Web App](https://ambitious-stone-0033b3603.1.azurestaticapps.net/). + +## Contributing + +We accept contributions to this project. However be aware that we are mainly working on a private backlog, so not everyone will be immediately obvious. If you want to get started on contributing, please read the [contribute space](https://github.com/umbraco/Umbraco.CMS.Backoffice/contribute) where you will be able to find the guidelines on how to contribute as well as a list of good first issues. diff --git a/src/Umbraco.Web.UI.Client/.github/pull_request_template b/src/Umbraco.Web.UI.Client/.github/pull_request_template new file mode 100644 index 0000000000..01c2c0fe8a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/.github/pull_request_template @@ -0,0 +1,30 @@ + + +## Description + + + +## Types of changes + + + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to change) +- [ ] Chore (minor updates related to the tooling or maintenance of the repository, does not impact compiled assets) + +## Motivation and context + + + +## How to test? + +## Screenshots (if appropriate) + +## Checklist + + + +- [ ] If my change requires a change to the documentation, I have updated the documentation in this pull request. +- [ ] I have read the **[CONTRIBUTING](<(https://github.com/umbraco/Umbraco.CMS.Backoffice/blob/main/.github/CONTRIBUTING.md)>)** document. +- [ ] I have added tests to cover my changes. diff --git a/src/Umbraco.Web.UI.Client/LICENSE b/src/Umbraco.Web.UI.Client/LICENSE new file mode 100644 index 0000000000..f88d3c2e3c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Umbraco HQ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/Umbraco.Web.UI.Client/libs/context-api/consume/context-consumer.controller.ts b/src/Umbraco.Web.UI.Client/libs/context-api/consume/context-consumer.controller.ts index 20fdb24379..534cd6a04f 100644 --- a/src/Umbraco.Web.UI.Client/libs/context-api/consume/context-consumer.controller.ts +++ b/src/Umbraco.Web.UI.Client/libs/context-api/consume/context-consumer.controller.ts @@ -1,7 +1,7 @@ -import type { UmbControllerHostInterface, UmbControllerInterface } from '@umbraco-cms/controller'; import { UmbContextToken } from '../context-token'; import { UmbContextConsumer } from './context-consumer'; import { UmbContextCallback } from './context-request.event'; +import type { UmbControllerHostInterface, UmbControllerInterface } from '@umbraco-cms/controller'; export class UmbContextConsumerController extends UmbContextConsumer diff --git a/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.controller.ts b/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.controller.ts index 88bebc98d2..f5ec707687 100644 --- a/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.controller.ts +++ b/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.controller.ts @@ -20,6 +20,7 @@ export class UmbContextProviderController } public destroy() { + super.destroy(); if (this.host) { this.host.removeController(this); } diff --git a/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.ts b/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.ts index f883c75c6a..ddad843889 100644 --- a/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.ts +++ b/src/Umbraco.Web.UI.Client/libs/context-api/provide/context-provider.ts @@ -53,4 +53,10 @@ export class UmbContextProvider { event.stopPropagation(); event.callback(this.#instance); }; + + + destroy(): void { + // I want to make sure to call this, but for now it was too overwhelming to require the destroy method on context instances. + (this.#instance as any).destroy?.(); + }; } diff --git a/src/Umbraco.Web.UI.Client/libs/controller/controller-host.mixin.ts b/src/Umbraco.Web.UI.Client/libs/controller/controller-host.mixin.ts index bce6f4815c..6c0342846b 100644 --- a/src/Umbraco.Web.UI.Client/libs/controller/controller-host.mixin.ts +++ b/src/Umbraco.Web.UI.Client/libs/controller/controller-host.mixin.ts @@ -1,5 +1,5 @@ -import type { HTMLElementConstructor } from '@umbraco-cms/models'; import { UmbControllerInterface } from './controller.interface'; +import type { HTMLElementConstructor } from '@umbraco-cms/models'; export declare class UmbControllerHostInterface extends HTMLElement { //#controllers:UmbController[]; diff --git a/src/Umbraco.Web.UI.Client/libs/notification/notification.service.ts b/src/Umbraco.Web.UI.Client/libs/notification/notification.service.ts index 1427ecb223..f240b1ec3b 100644 --- a/src/Umbraco.Web.UI.Client/libs/notification/notification.service.ts +++ b/src/Umbraco.Web.UI.Client/libs/notification/notification.service.ts @@ -1,6 +1,6 @@ import { BehaviorSubject } from 'rxjs'; -import { UmbContextToken } from '@umbraco-cms/context-api'; import { UmbNotificationHandler } from './notification-handler'; +import { UmbContextToken } from '@umbraco-cms/context-api'; export type UmbNotificationData = any; diff --git a/src/Umbraco.Web.UI.Client/libs/notification/notification.stories.ts b/src/Umbraco.Web.UI.Client/libs/notification/notification.stories.ts index a3f6a63c6a..c890834387 100644 --- a/src/Umbraco.Web.UI.Client/libs/notification/notification.stories.ts +++ b/src/Umbraco.Web.UI.Client/libs/notification/notification.stories.ts @@ -4,7 +4,6 @@ import { Meta, Story } from '@storybook/web-components'; import { html } from 'lit'; import { customElement } from 'lit/decorators.js'; -import { UmbLitElement } from '@umbraco-cms/element'; import type { UmbNotificationDefaultData } from './layouts/default'; import { UmbNotificationColor, @@ -12,6 +11,7 @@ import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, } from '.'; +import { UmbLitElement } from '@umbraco-cms/element'; export default { title: 'API/Notifications/Overview', diff --git a/src/Umbraco.Web.UI.Client/libs/observable-api/deep-state.ts b/src/Umbraco.Web.UI.Client/libs/observable-api/deep-state.ts index 03f2097033..a0cd9f94fe 100644 --- a/src/Umbraco.Web.UI.Client/libs/observable-api/deep-state.ts +++ b/src/Umbraco.Web.UI.Client/libs/observable-api/deep-state.ts @@ -1,4 +1,5 @@ import { BehaviorSubject } from "rxjs"; +import { createObservablePart } from "./create-observable-part.method"; // TODO: Should this handle array as well? @@ -50,6 +51,13 @@ export class DeepState extends BehaviorSubject { super(deepFreeze(initialData)); } + getObservablePart( + mappingFunction: MappingFunction, + memoizationFunction?: MemoizationFunction + ) { + return createObservablePart(this, mappingFunction, memoizationFunction); + } + next(newData: T): void { const frozenData = deepFreeze(newData); // Only update data if its different than current data. diff --git a/src/Umbraco.Web.UI.Client/libs/resources/tryExecuteAndNotify.method.ts b/src/Umbraco.Web.UI.Client/libs/resources/tryExecuteAndNotify.method.ts index 2bd9c26d61..8a280ec876 100644 --- a/src/Umbraco.Web.UI.Client/libs/resources/tryExecuteAndNotify.method.ts +++ b/src/Umbraco.Web.UI.Client/libs/resources/tryExecuteAndNotify.method.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { UmbResourceController } from './resource.controller'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import type { UmbNotificationOptions } from '@umbraco-cms/notification'; -import { UmbResourceController } from './resource.controller'; export function tryExecuteAndNotify( host: UmbControllerHostInterface, diff --git a/src/Umbraco.Web.UI.Client/libs/store/store.ts b/src/Umbraco.Web.UI.Client/libs/store/store.ts index 0be93c43ea..e87dab36d7 100644 --- a/src/Umbraco.Web.UI.Client/libs/store/store.ts +++ b/src/Umbraco.Web.UI.Client/libs/store/store.ts @@ -25,12 +25,21 @@ export interface UmbTreeStore extends UmbDataStore { trash(keys: string[]): Promise; } -export interface UmbContentStore extends UmbDataStore { +export interface UmbEntityDetailStore extends UmbDataStore { + + /** + * @description - Request scaffold data by entityType and . The data is added to the store and is returned as an Observable. + * @param {string} key + * @return {*} {T} + * @memberof UmbEntityDetailStore + */ + getScaffold: (entityType: string, parentKey: string | null) => T; + /** * @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 + * @memberof UmbEntityDetailStore */ getByKey(key: string): Observable; @@ -38,7 +47,15 @@ export interface UmbContentStore extends UmbDataStore { * @description - Save data. * @param {object} data * @return {*} {(Promise)} - * @memberof UmbContentStore + * @memberof UmbEntityDetailStore */ save(data: T[]): Promise; } + + +export interface UmbContentStore extends UmbEntityDetailStore { + + // TODO: make something that is specific for UmbContentStore, or then we should get rid of it. But for now i kept it as we might want this for rollback or other things specific to Content types. + save(data: T[]): Promise; + +} 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 1d684f6192..217ba77ce6 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts @@ -2,7 +2,6 @@ import { defineElement } from '@umbraco-ui/uui-base/lib/registration'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { css, html } from 'lit'; - import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '../core/modal'; import { UmbUserStore } from './users/users/user.store'; import { UmbUserGroupStore } from './users/user-groups/user-group.store'; @@ -12,9 +11,12 @@ import { UMB_CURRENT_USER_HISTORY_STORE_CONTEXT_TOKEN, } from './users/current-user/current-user-history.store'; -import { UmbBackofficeContext, UMB_BACKOFFICE_CONTEXT_TOKEN } from './shared/components/backoffice-frame/backoffice.context'; -import {UmbDocumentTypeDetailStore} from './documents/document-types/document-type.detail.store'; -import {UmbDocumentTypeTreeStore} from './documents/document-types/document-type.tree.store'; +import { + UmbBackofficeContext, + UMB_BACKOFFICE_CONTEXT_TOKEN, +} from './shared/components/backoffice-frame/backoffice.context'; +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 { UmbDocumentDetailStore } from './documents/documents/document.detail.store'; @@ -31,9 +33,9 @@ import { UmbDocumentBlueprintTreeStore } from './documents/document-blueprints/d import { UmbDataTypeDetailStore } from './settings/data-types/data-type.detail.store'; import { UmbDataTypeTreeStore } from './settings/data-types/data-type.tree.store'; +import { UmbThemeService, UMB_THEME_SERVICE_CONTEXT_TOKEN } from './themes/theme.service'; import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification'; - // Domains import './settings'; import './documents'; @@ -94,6 +96,7 @@ export class UmbBackofficeElement extends UmbLitElement { this.provideContext(UMB_BACKOFFICE_CONTEXT_TOKEN, new UmbBackofficeContext()); this.provideContext(UMB_CURRENT_USER_HISTORY_STORE_CONTEXT_TOKEN, new UmbCurrentUserHistoryStore()); + this.provideContext(UMB_THEME_SERVICE_CONTEXT_TOKEN, new UmbThemeService()); } 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 index 3e44801258..5ef48f1ba7 100644 --- 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 @@ -44,6 +44,11 @@ export class UmbDocumentBlueprintDetailStore extends UmbStoreBase { ); } + getScaffold(entityType: string, parentKey: string | null) { + return { + } as DocumentBlueprintDetails; + } + // TODO: make sure UI somehow can follow the status of this action. /** * @description - Save a DocumentBlueprint. 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 index 62b8c31c5d..4835577c3a 100644 --- 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 @@ -1,7 +1,7 @@ import type { DocumentTypeDetails } from '@umbraco-cms/models'; import { UmbContextToken } from '@umbraco-cms/context-api'; import { createObservablePart, ArrayState } from '@umbraco-cms/observable-api'; -import { UmbStoreBase } from '@umbraco-cms/store'; +import { UmbEntityDetailStore, UmbStoreBase } from '@umbraco-cms/store'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; @@ -14,8 +14,7 @@ export const UMB_DOCUMENT_TYPE_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken< * @extends {UmbStoreBase} * @description - Details Data Store for Document Types */ -export class UmbDocumentTypeDetailStore extends UmbStoreBase { - +export class UmbDocumentTypeDetailStore extends UmbStoreBase implements UmbEntityDetailStore { #data = new ArrayState([], (x) => x.key); @@ -24,6 +23,20 @@ export class UmbDocumentTypeDetailStore extends UmbStoreBase { super(host, UMB_DOCUMENT_TYPE_DETAIL_STORE_CONTEXT_TOKEN.toString()); } + + getScaffold(entityType: string, parentKey: string | null) { + return { + key: '', + name: '', + icon: '', + type: '', + hasChildren: false, + parentKey: '', + alias: '', + properties: [], + } as DocumentTypeDetails; + } + /** * @description - Request a Data Type by key. The Data Type is added to the store and is returned as an Observable. * @param {string} key 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 0214a2a2c4..949cb7f5e4 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,29 +1,32 @@ -import { UmbWorkspaceContentContext } from '../../../shared/components/workspace/workspace-content/workspace-content.context'; -import { - UmbDocumentTypeDetailStore, - UMB_DOCUMENT_TYPE_DETAIL_STORE_CONTEXT_TOKEN, -} from '../document-type.detail.store'; -import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { UmbEntityWorkspaceManager } from '../../../shared/components/workspace/workspace-context/entity-manager-controller'; +import { UmbWorkspaceContext } from '../../../shared/components/workspace/workspace-context/workspace-context'; +import { UmbWorkspaceEntityContextInterface } from '../../../shared/components/workspace/workspace-context/workspace-entity-context.interface'; +import { UMB_DOCUMENT_TYPE_DETAIL_STORE_CONTEXT_TOKEN } from '../document-type.detail.store'; import type { DocumentTypeDetails } from '@umbraco-cms/models'; -const DefaultDocumentTypeData = { - key: '', - name: '', - icon: '', - type: '', - hasChildren: false, - parentKey: '', - alias: '', - properties: [], -} as DocumentTypeDetails; +export class UmbWorkspaceDocumentTypeContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { -export class UmbWorkspaceDocumentTypeContext extends UmbWorkspaceContentContext< - DocumentTypeDetails, - UmbDocumentTypeDetailStore -> { - constructor(host: UmbControllerHostInterface) { - super(host, DefaultDocumentTypeData, UMB_DOCUMENT_TYPE_DETAIL_STORE_CONTEXT_TOKEN.toString(), 'documentType'); + #manager = new UmbEntityWorkspaceManager(this._host, 'document-type', UMB_DOCUMENT_TYPE_DETAIL_STORE_CONTEXT_TOKEN); + + public readonly data = this.#manager.state.asObservable(); + public readonly name = this.#manager.state.getObservablePart((state) => state?.name); + + + setName(name: string) { + this.#manager.state.update({name: name}) } + setIcon(icon: string) { + this.#manager.state.update({icon: icon}) + } + getEntityType = this.#manager.getEntityType; + getUnique = this.#manager.getEntityKey; + getEntityKey = this.#manager.getEntityKey; + getStore = this.#manager.getStore; + getData = this.#manager.getData; + load = this.#manager.load; + create = this.#manager.create; + save = this.#manager.save; + destroy = this.#manager.destroy; public setPropertyValue(alias: string, value: unknown) { throw new Error('setPropertyValue is not implemented for UmbWorkspaceDocumentTypeContext'); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.element.ts index 3c799f13c7..f2aabc22ff 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/document-type-workspace.element.ts @@ -46,22 +46,6 @@ export class UmbDocumentTypeWorkspaceElement extends UmbLitElement implements Um name: 'umb:document-dashed-line', }; - private _entityKey!: string; - @property() - public get entityKey(): string { - return this._entityKey; - } - public set entityKey(value: string) { - this._entityKey = value; - if (this._entityKey) { - this._workspaceContext?.load(this._entityKey); - } - } - - @property() - public set create(parentKey: string | null) { - this._workspaceContext?.create(parentKey); - } private _workspaceContext: UmbWorkspaceDocumentTypeContext = new UmbWorkspaceDocumentTypeContext(this); @@ -83,13 +67,21 @@ export class UmbDocumentTypeWorkspaceElement extends UmbLitElement implements Um }); } + public load(entityKey: string) { + this._workspaceContext.load(entityKey); + } + + public create(parentKey: string | null) { + this._workspaceContext.create(parentKey); + } + // TODO. find a way where we don't have to do this for all workspaces. private _handleInput(event: UUIInputEvent) { if (event instanceof UUIInputEvent) { const target = event.composedPath()[0] as UUIInputElement; if (typeof target?.value === 'string') { - this._workspaceContext?.update({ name: target.value }); + this._workspaceContext?.setName(target.value); } } } @@ -98,7 +90,7 @@ export class UmbDocumentTypeWorkspaceElement extends UmbLitElement implements Um const modalHandler = this._modalService?.iconPicker(); modalHandler?.onClose().then((saved) => { - if (saved) this._workspaceContext?.update({ icon: saved.icon }); + if (saved) this._workspaceContext?.setIcon(saved.icon); console.log(saved); // TODO save color ALIAS as well }); 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 d02ab4c941..f8dcb37367 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 @@ -38,6 +38,37 @@ export class UmbDocumentDetailStore extends UmbStoreBase implements UmbContentSt ); } + getScaffold(entityType: string, parentKey: string | null) { + return { + key: '', + name: '', + icon: '', + type: '', + hasChildren: false, + parentKey: '', + isTrashed: false, + properties: [ + { + alias: '', + label: '', + description: '', + dataTypeKey: '', + }, + ], + data: [ + { + alias: '', + value: '', + }, + ], + variants: [ + { + name: '', + }, + ], + } as DocumentDetails; + } + // TODO: make sure UI somehow can follow the status of this action. save(data: DocumentDetails[]) { // fetch from server and update store 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 0346072f0a..8c27b2b686 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 @@ -39,6 +39,19 @@ export class UmbDocumentTreeStore extends UmbStoreBase implements UmbTreeStore, destination: string) { + // TODO: use backend cli when available. + const res = await fetch('/umbraco/management/api/v1/document/move', { + method: 'POST', + body: JSON.stringify({ keys, destination }), + headers: { + 'Content-Type': 'application/json', + }, + }); + const data = await res.json(); + this._data.append(data); + } + getTreeRoot() { tryExecuteAndNotify(this._host, DocumentResource.getTreeDocumentRoot({})).then(({ data }) => { if (data) { 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 3f89ad1f4b..6a6d0838f8 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,56 +1,72 @@ -import { UmbWorkspaceContentContext } from '../../../shared/components/workspace/workspace-content/workspace-content.context'; 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 { UmbWorkspaceEntityContextInterface } from '../../../shared/components/workspace/workspace-context/workspace-entity-context.interface'; +import { UmbWorkspaceContext } from '../../../shared/components/workspace/workspace-context/workspace-context'; +import { UmbEntityWorkspaceManager } from '../../../shared/components/workspace/workspace-context/entity-manager-controller'; import type { DocumentDetails } from '@umbraco-cms/models'; import { appendToFrozenArray } from '@umbraco-cms/observable-api'; -const DefaultDocumentData = { - key: '', - name: '', - icon: '', - type: '', - hasChildren: false, - parentKey: '', - isTrashed: false, - properties: [ - { - alias: '', - label: '', - description: '', - dataTypeKey: '', - }, - ], - data: [ - { - alias: '', - value: '', - }, - ], - variants: [ - { - name: '', - }, - ], -} as DocumentDetails; +export class UmbDocumentWorkspaceContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { -export class UmbWorkspaceDocumentContext extends UmbWorkspaceContentContext { - constructor(host: UmbControllerHostInterface) { - super(host, DefaultDocumentData, UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN.toString(), 'document'); + // Repository notes: + /* - console.log("UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN", UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN.toString()) + #draft = new ObjectState(undefined); + + + */ + + // Manager will be removed when we get the Repository: + #manager = new UmbEntityWorkspaceManager(this._host, 'document', UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN); + + public readonly data = this.#manager.state.asObservable(); + public readonly name = this.#manager.state.getObservablePart((state) => state?.name); + + setName(name: string) { + this.#manager.state.update({name: name}) + } + getEntityType = this.#manager.getEntityType; + getUnique = this.#manager.getEntityKey; + getEntityKey = this.#manager.getEntityKey; + getStore = this.#manager.getStore; + getData = this.#manager.getData; + load = this.#manager.load; + create = this.#manager.create; + save = this.#manager.save; + destroy = this.#manager.destroy; + + /** + * Concept for Repository impl.: + + load(entityKey: string) { + this.#repository.load(entityKey).then((data) => { + this.#draft.next(data) + }) } - public setPropertyValue(alias: string, value: unknown) { + create(parentKey: string | undefined) { + this.#repository.create(parentKey).then((data) => { + this.#draft.next(data) + }) + } + + */ + + + // This could eventually be moved out as well? + 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? const entry = {alias: alias, value: value}; - const newDataSet = appendToFrozenArray(this._data.getValue().data, entry, x => x.alias); + const currentData = this.#manager.getData(); + if (currentData) { + const newDataSet = appendToFrozenArray(currentData.data, entry, x => x.alias); - this._data.update({data: newDataSet}); + this.#manager.state.update({data: newDataSet}); + } } + + /* concept notes: diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.element.ts index 7f3f28b048..55ddd6a39d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.element.ts @@ -1,9 +1,9 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { css, html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { UmbWorkspaceDocumentContext } from './document-workspace.context'; +import type { UmbWorkspaceEntityElement } from '../../../shared/components/workspace/workspace-entity-element.interface'; +import { UmbDocumentWorkspaceContext } from './document-workspace.context'; import { UmbLitElement } from '@umbraco-cms/element'; -import type { UmbWorkspaceEntityElement } from 'src/backoffice/shared/components/workspace/workspace-entity-element.interface'; @customElement('umb-document-workspace') export class UmbDocumentWorkspaceElement extends UmbLitElement implements UmbWorkspaceEntityElement { @@ -18,25 +18,16 @@ export class UmbDocumentWorkspaceElement extends UmbLitElement implements UmbWor `, ]; - private _entityKey!: string; - @property() - public get entityKey(): string { - return this._entityKey; - } - public set entityKey(value: string) { - this._entityKey = value; - if (this._entityKey) { - this._workspaceContext.load(this._entityKey); - } + private _workspaceContext: UmbDocumentWorkspaceContext = new UmbDocumentWorkspaceContext(this); + + public load(entityKey: string) { + this._workspaceContext.load(entityKey); } - @property() - public set create(parentKey: string | null) { + public create(parentKey: string | null) { this._workspaceContext.create(parentKey); } - private _workspaceContext: UmbWorkspaceDocumentContext = new UmbWorkspaceDocumentContext(this); - render() { return html``; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.test.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.test.ts index e15d51b5da..3849929e42 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.test.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/workspace/document-workspace.test.ts @@ -18,12 +18,12 @@ describe('UmbDocumentWorkspaceElement', () => { await expect(element).to.be.accessible(defaultA11yConfig); }); - describe('properties', () => { - it('has a entityKey property', () => { - expect(element).to.have.property('entityKey'); + describe('methods', () => { + it('has a load method', () => { + expect(element).to.have.property('load').that.is.a('function'); }); - it('has a create property', () => { - expect(element).to.have.property('create'); + it('has a create method', () => { + expect(element).to.have.property('create').that.is.a('function'); }); }); }); 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 index bc32dbd8f1..f27ba99859 100644 --- 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 @@ -1,7 +1,7 @@ -import type { DataTypeDetails } from '@umbraco-cms/models'; +import type { DataTypeDetails, MediaTypeDetails } from '@umbraco-cms/models'; import { UmbContextToken } from '@umbraco-cms/context-api'; import { ArrayState } from '@umbraco-cms/observable-api'; -import { UmbStoreBase } from '@umbraco-cms/store'; +import { UmbEntityDetailStore, UmbStoreBase } from '@umbraco-cms/store'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; @@ -14,16 +14,22 @@ export const UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken { - private _data = new ArrayState([], (x) => x.key); + private _data = new ArrayState([], (x) => x.key); constructor(host: UmbControllerHostInterface) { super(host, UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT_TOKEN.toString()); } + + getScaffold(entityType: string, parentKey: string | null) { + return { + } as MediaTypeDetails; + } + /** * @description - Request a Data Type by key. The Data Type is added to the store and is returned as an Observable. * @param {string} key @@ -36,12 +42,12 @@ export class UmbMediaTypeDetailStore extends UmbStoreBase { // TODO: make sure UI somehow can follow the status of this action. /** - * @description - Save a Data Type. - * @param {Array} dataTypes + * @description - Save a Media Type. + * @param {Array} mediaTypes * @memberof UmbMediaTypesStore * @return {*} {Promise} */ - save(data: DataTypeDetails[]) { + save(data: MediaTypeDetails[]) { return null as any; } 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 8783a9a6a2..4ba307da9c 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 @@ -17,7 +17,7 @@ export const UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken { - #data = new ArrayState([], (x) => x.key); + #data = new ArrayState([], (x) => x.key); constructor(host: UmbControllerHostInterface) { @@ -37,6 +37,32 @@ export class UmbMediaDetailStore extends UmbStoreBase implements UmbContentStore ); } + getScaffold(entityType: string, parentKey: string | null) { + return { + key: '', + name: '', + icon: '', + type: '', + hasChildren: false, + parentKey: '', + isTrashed: false, + properties: [ + { + alias: '', + label: '', + description: '', + dataTypeKey: '', + }, + ], + data: [ + { + alias: '', + value: '', + }, + ] + } as MediaDetails; + } + // TODO: make sure UI somehow can follow the status of this action. save(data: MediaDetails[]) { // fetch from server and update store 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 14325afec6..d4f4cd51e5 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,41 +1,29 @@ -import { UmbWorkspaceContentContext } from '../../../shared/components/workspace/workspace-content/workspace-content.context'; -import { UmbMediaDetailStore, UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN } from '../../../media/media/media.detail.store'; -import { UmbControllerHostInterface } from '@umbraco-cms/controller'; -import type { MediaDetails } from '@umbraco-cms/models'; +import { UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN } from "../media.detail.store"; +import { UmbEntityWorkspaceManager } from "../../../shared/components/workspace/workspace-context/entity-manager-controller"; +import { UmbWorkspaceContext } from "../../../shared/components/workspace/workspace-context/workspace-context"; +import { UmbWorkspaceEntityContextInterface } from "../../../shared/components/workspace/workspace-context/workspace-entity-context.interface"; +import type { MediaDetails } from "@umbraco-cms/models"; -const DefaultMediaData = { - key: '', - name: '', - icon: '', - type: '', - hasChildren: false, - parentKey: '', - isTrashed: false, - properties: [ - { - alias: '', - label: '', - description: '', - dataTypeKey: '', - }, - ], - data: [ - { - alias: '', - value: '', - }, - ], - variants: [ - { - name: '', - }, - ], -} as MediaDetails; +export class UmbWorkspaceMediaContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { -export class UmbWorkspaceMediaContext extends UmbWorkspaceContentContext { - constructor(host: UmbControllerHostInterface) { - super(host, DefaultMediaData, UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN.toString(), 'media'); + + #manager = new UmbEntityWorkspaceManager(this._host, 'media', UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN); + + public readonly data = this.#manager.state.asObservable(); + public readonly name = this.#manager.state.getObservablePart((state) => state?.name); + + setName(name: string) { + this.#manager.state.update({name: name}) } + getEntityType = this.#manager.getEntityType; + getUnique = this.#manager.getEntityKey; + getEntityKey = this.#manager.getEntityKey; + getStore = this.#manager.getStore; + getData = this.#manager.getData; + load = this.#manager.load; + create = this.#manager.create; + save = this.#manager.save; + destroy = this.#manager.destroy; public setPropertyValue(alias: string, value: unknown) { throw new Error('setPropertyValue is not implemented for UmbWorkspaceMediaContext'); 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 index 8f2f06aec1..2189c2c274 100644 --- 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 @@ -3,7 +3,7 @@ import type { MemberGroupDetails } from '@umbraco-cms/models'; import { UmbContextToken } from '@umbraco-cms/context-api'; import { ArrayState } from '@umbraco-cms/observable-api'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; -import { UmbStoreBase } from '@umbraco-cms/store'; +import { UmbEntityDetailStore, UmbStoreBase } from '@umbraco-cms/store'; export const UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbMemberGroupStore'); @@ -13,7 +13,7 @@ export const UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN = new UmbContextToken { #groups = new ArrayState([], x => x.key); @@ -24,11 +24,16 @@ export class UmbMemberGroupStore extends UmbStoreBase { super(host, UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN.toString()); } - getByKey(key: string): Observable { + getScaffold(entityType: string, parentKey: string | null) { + return { + } as MemberGroupDetails; + } + + getByKey(key: string): Observable { return null as any; } - async save(mediaTypes: Array): Promise { + async save(memberGroups: Array): Promise { return null as any; } } 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 index f74ea20e1c..25511b12e4 100644 --- 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 @@ -1,7 +1,7 @@ import type { MemberTypeDetails } from '@umbraco-cms/models'; import { UmbContextToken } from '@umbraco-cms/context-api'; import { ArrayState } from '@umbraco-cms/observable-api'; -import { UmbStoreBase } from '@umbraco-cms/store'; +import { UmbEntityDetailStore, UmbStoreBase } from '@umbraco-cms/store'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; @@ -14,7 +14,7 @@ export const UMB_MEMBER_TYPE_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken { #data = new ArrayState([], (x) => x.key); @@ -24,6 +24,11 @@ export class UmbMemberTypeDetailStore extends UmbStoreBase { super(host, UMB_MEMBER_TYPE_DETAIL_STORE_CONTEXT_TOKEN.toString()); } + getScaffold(entityType: string, parentKey: string | null) { + return { + } as MemberTypeDetails; + } + /** * @description - Request a Data Type by key. The Data Type is added to the store and is returned as an Observable. * @param {string} key diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/created/created-packages-section-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/created/created-packages-section-view.element.ts index 04925967b7..be18117d20 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/created/created-packages-section-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/created/created-packages-section-view.element.ts @@ -2,8 +2,7 @@ import { html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { IRoute, IRoutingInfo } from 'router-slot'; import type { ManifestWorkspace } from '@umbraco-cms/models'; -import { createExtensionElement } from '@umbraco-cms/extensions-api'; -import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api'; +import { createExtensionElement , umbExtensionsRegistry } from '@umbraco-cms/extensions-api'; import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-created-packages-section-view') diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/installed/installed-packages-section-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/installed/installed-packages-section-view.element.ts index d5e7bfd180..f1b8d80ddc 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/installed/installed-packages-section-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/installed/installed-packages-section-view.element.ts @@ -2,8 +2,7 @@ import { html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { IRoute, IRoutingInfo } from 'router-slot'; import type { ManifestWorkspace } from '@umbraco-cms/models'; -import { createExtensionElement } from '@umbraco-cms/extensions-api'; -import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api'; +import { createExtensionElement , umbExtensionsRegistry } from '@umbraco-cms/extensions-api'; import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-installed-packages-section-view') diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/installed/packages-installed-item.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/installed/packages-installed-item.element.ts index 0e80b2b45f..0ef2ee1f54 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/installed/packages-installed-item.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/packages/package-section/views/installed/packages-installed-item.element.ts @@ -3,9 +3,8 @@ import { customElement, property, state } from 'lit/decorators.js'; import { firstValueFrom, map } from 'rxjs'; import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '../../../../../core/modal'; -import { createExtensionElement } from '@umbraco-cms/extensions-api'; +import { createExtensionElement , umbExtensionsRegistry } from '@umbraco-cms/extensions-api'; -import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api'; import type { ManifestPackageView } from '@umbraco-cms/models'; import { UmbLitElement } from '@umbraco-cms/element'; 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 0704f2b0b6..6ea6dc4fec 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 @@ -1,10 +1,8 @@ import type { DataTypeDetails } from '@umbraco-cms/models'; import { UmbContextToken } from '@umbraco-cms/context-api'; import { createObservablePart, ArrayState } from '@umbraco-cms/observable-api'; -import { UmbStoreBase } from '@umbraco-cms/store'; +import { UmbEntityDetailStore, UmbStoreBase } from '@umbraco-cms/store'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; -import { DataTypeResource } from '@umbraco-cms/backend-api'; -import { tryExecuteAndNotify } from '@umbraco-cms/resources'; export const UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbDataTypeDetailStore'); @@ -16,7 +14,7 @@ export const UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken { #data = new ArrayState([], (x) => x.key); @@ -26,6 +24,21 @@ export class UmbDataTypeDetailStore extends UmbStoreBase { super(host, UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN.toString()); } + + getScaffold(entityType: string, parentKey: string | null) { + return { + key: '', + name: '', + icon: '', + type: 'data-type', + hasChildren: false, + parentKey: '', + propertyEditorModelAlias: '', + propertyEditorUIAlias: '', + data: [], + } as DataTypeDetails; + } + /** * @description - Request a Data Type by key. The Data Type is added to the store and is returned as an Observable. * @param {string} key 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 de3d6619fd..fe4c51aabd 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,40 +1,51 @@ -import { UmbWorkspaceContentContext } from '../../../shared/components/workspace/workspace-content/workspace-content.context'; -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 { UmbWorkspaceContext } from '../../../shared/components/workspace/workspace-context/workspace-context'; +import { UmbWorkspaceEntityContextInterface } from '../../../shared/components/workspace/workspace-context/workspace-entity-context.interface'; +import { UmbEntityWorkspaceManager } from '../../../shared/components/workspace/workspace-context/entity-manager-controller'; +import { UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN } from '../data-type.detail.store'; +import type { DataTypeDetails } from '@umbraco-cms/models'; import { appendToFrozenArray } from '@umbraco-cms/observable-api'; -const DefaultDataTypeData = { - key: '', - name: '', - icon: '', - type: 'data-type', - hasChildren: false, - parentKey: '', - propertyEditorModelAlias: '', - propertyEditorUIAlias: '', - data: [], -} as DataTypeDetails; +export class UmbWorkspaceDataTypeContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { -export class UmbWorkspaceDataTypeContext extends UmbWorkspaceContentContext< - DataTypeDetails, - UmbDataTypeDetailStore -> { - constructor(host: UmbControllerHostInterface) { - super(host, DefaultDataTypeData, UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN.toString(), 'dataType'); + #manager = new UmbEntityWorkspaceManager(this._host, 'data-type', UMB_DATA_TYPE_DETAIL_STORE_CONTEXT_TOKEN); + + + + + + public readonly data = this.#manager.state.asObservable(); + public readonly name = this.#manager.state.getObservablePart((state) => state?.name); + + setName(name: string) { + this.#manager.state.update({name: name}); } + setPropertyEditorModelAlias(alias?: string) { + this.#manager.state.update({propertyEditorModelAlias: alias}); + } + setPropertyEditorUIAlias(alias?: string) { + this.#manager.state.update({propertyEditorUIAlias: alias}); + } + getEntityType = this.#manager.getEntityType; + getUnique = this.#manager.getEntityKey; + getEntityKey = this.#manager.getEntityKey; + getStore = this.#manager.getStore; + getData = this.#manager.getData; + load = this.#manager.load; + create = this.#manager.create; + save = this.#manager.save; + destroy = this.#manager.destroy; - 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? - const entry = { alias: alias, value: value }; - const newDataSet = appendToFrozenArray( - (this._data.getValue() as DataTypeDetails).data, - entry, - (x: DataTypePropertyData) => x.alias - ); + // This could eventually be moved out as well? + setPropertyValue(alias: string, value: unknown) { - this.update({ data: newDataSet }); + const entry = {alias: alias, value: value}; + + const currentData = this.#manager.getData(); + if (currentData) { + const newDataSet = appendToFrozenArray(currentData.data, entry, x => x.alias); + + this.#manager.state.update({data: newDataSet}); + } } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.element.ts index 51c2ecdd44..583f56c2e9 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.element.ts @@ -67,7 +67,7 @@ export class UmbDataTypeWorkspaceElement extends UmbLitElement { const target = event.composedPath()[0] as UUIInputElement; if (typeof target?.value === 'string') { - this._workspaceContext.update({ name: target.value }); + this._workspaceContext.setName(target.value); } } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/edit/data-type-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/edit/data-type-workspace-view-edit.element.ts index 15555d9fee..7757f2c912 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/edit/data-type-workspace-view-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/views/edit/data-type-workspace-view-edit.element.ts @@ -69,7 +69,7 @@ export class UmbDataTypeWorkspaceViewEditElement extends UmbLitElement { this._dataType = dataType as DataTypeDetails; if (this._dataType.propertyEditorUIAlias !== this._propertyEditorUIAlias) { - this._observePropertyEditorUI(this._dataType.propertyEditorUIAlias); + this._observePropertyEditorUI(this._dataType.propertyEditorUIAlias || undefined); } if (this._dataType.data !== this._data) { @@ -78,7 +78,7 @@ export class UmbDataTypeWorkspaceViewEditElement extends UmbLitElement { }); } - private _observePropertyEditorUI(propertyEditorUIAlias: string | null) { + private _observePropertyEditorUI(propertyEditorUIAlias?: string) { if (!propertyEditorUIAlias) return; this.observe( @@ -92,7 +92,7 @@ export class UmbDataTypeWorkspaceViewEditElement extends UmbLitElement { this._propertyEditorUIIcon = propertyEditorUI?.meta.icon ?? ''; this._propertyEditorModelAlias = propertyEditorUI?.meta.propertyEditorModel ?? ''; - this._workspaceContext?.update({ propertyEditorModelAlias: this._propertyEditorModelAlias }); + this._workspaceContext?.setPropertyEditorModelAlias(this._propertyEditorModelAlias); } ); } @@ -110,9 +110,9 @@ export class UmbDataTypeWorkspaceViewEditElement extends UmbLitElement { }); } - private _selectPropertyEditorUI(propertyEditorUIAlias: string | null) { + private _selectPropertyEditorUI(propertyEditorUIAlias: string | undefined) { if (!this._dataType || this._dataType.propertyEditorUIAlias === propertyEditorUIAlias) return; - this._workspaceContext?.update({ propertyEditorUIAlias }); + this._workspaceContext?.setPropertyEditorUIAlias(propertyEditorUIAlias); this._observePropertyEditorUI(propertyEditorUIAlias); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/extensions/workspace/extension-root-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/extensions/workspace/extension-root-workspace.element.ts index 4fa338ebae..26bcf43f2a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/extensions/workspace/extension-root-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/extensions/workspace/extension-root-workspace.element.ts @@ -1,7 +1,6 @@ import { html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; -import { isManifestElementNameType } from '@umbraco-cms/extensions-api'; -import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api'; +import { isManifestElementNameType , umbExtensionsRegistry } from '@umbraco-cms/extensions-api'; import type { ManifestBase } from '@umbraco-cms/models'; import { UmbLitElement } from '@umbraco-cms/element'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/bulk-actions/collection-bulk-action-media-delete.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/bulk-actions/collection-bulk-action-media-delete.element.ts index afe8160c3c..871c85b58b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/bulk-actions/collection-bulk-action-media-delete.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/bulk-actions/collection-bulk-action-media-delete.element.ts @@ -6,7 +6,6 @@ import { map } from 'rxjs'; import { repeat } from 'lit-html/directives/repeat.js'; import { UmbCollectionContext, UMB_COLLECTION_CONTEXT_TOKEN } from '../collection.context'; import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '../../../../core/modal'; -import { UmbMediaTreeStore, UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN } from '../../../media/media/media.tree.store'; import { UmbLitElement } from '@umbraco-cms/element'; import type { ManifestCollectionBulkAction, MediaDetails } from '@umbraco-cms/models'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.element.ts index 54d24df995..d9a52420ca 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.element.ts @@ -5,9 +5,8 @@ import { map } from 'rxjs'; import './collection-selection-actions.element'; import './collection-toolbar.element'; import { UmbCollectionContext, UMB_COLLECTION_CONTEXT_TOKEN } from './collection.context'; -import { createExtensionElement } from '@umbraco-cms/extensions-api'; +import { createExtensionElement , umbExtensionsRegistry } from '@umbraco-cms/extensions-api'; import type { ManifestCollectionView, MediaDetails } from '@umbraco-cms/models'; -import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api'; import { UmbLitElement } from '@umbraco-cms/element'; import type { UmbObserverController } from '@umbraco-cms/observable-api'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/views/collection-view-media-table.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/views/collection-view-media-table.element.ts index 258a0a7465..86d53d6a1f 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/views/collection-view-media-table.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/views/collection-view-media-table.element.ts @@ -2,8 +2,6 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbCollectionContext, UMB_COLLECTION_CONTEXT_TOKEN } from '../collection.context'; -import type { MediaDetails } from '@umbraco-cms/models'; -import { UmbLitElement } from '@umbraco-cms/element'; import { UmbTableColumn, UmbTableConfig, @@ -13,6 +11,8 @@ import { UmbTableOrderedEvent, UmbTableSelectedEvent, } from '../../components/table'; +import type { MediaDetails } from '@umbraco-cms/models'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-collection-view-media-table') export class UmbCollectionViewMediaTableElement extends UmbLitElement { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/extension-slot/extension-slot.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/extension-slot/extension-slot.element.ts index 79af586bee..083a6a5181 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/extension-slot/extension-slot.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/extension-slot/extension-slot.element.ts @@ -3,8 +3,7 @@ import type { TemplateResult } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { map } from 'rxjs'; import { repeat } from 'lit/directives/repeat.js'; -import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api'; -import { createExtensionElement, isManifestElementableType } from '@umbraco-cms/extensions-api'; +import { umbExtensionsRegistry , createExtensionElement, isManifestElementableType } from '@umbraco-cms/extensions-api'; import { UmbLitElement } from '@umbraco-cms/element'; export type InitializedExtension = { alias: string; weight: number; component: HTMLElement | null }; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-dashboards/section-dashboards.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-dashboards/section-dashboards.element.ts index 3f6ae8fc09..2bd975f3c7 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-dashboards/section-dashboards.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-dashboards/section-dashboards.element.ts @@ -4,13 +4,12 @@ import { customElement, state } from 'lit/decorators.js'; import { IRoutingInfo } from 'router-slot'; import { first, map } from 'rxjs'; import { UmbSectionContext, UMB_SECTION_CONTEXT_TOKEN } from '../section.context'; -import { createExtensionElement } from '@umbraco-cms/extensions-api'; +import { createExtensionElement , umbExtensionsRegistry } from '@umbraco-cms/extensions-api'; import type { ManifestDashboard, ManifestDashboardCollection, ManifestWithMeta, } from '@umbraco-cms/models'; -import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api'; import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-section-dashboards') diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts index c60b1e147d..b4565ccee2 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section.element.ts @@ -1,13 +1,12 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { css, html, nothing } from 'lit'; import { customElement, state } from 'lit/decorators.js'; -import { map, switchMap, EMPTY, of } from 'rxjs'; +import { map } from 'rxjs'; import { IRoutingInfo } from 'router-slot'; import type { UmbWorkspaceEntityElement } from '../workspace/workspace-entity-element.interface'; import { UmbSectionContext, UMB_SECTION_CONTEXT_TOKEN } from './section.context'; -import { createExtensionElement } from '@umbraco-cms/extensions-api'; import type { ManifestSectionView, ManifestWorkspace, ManifestSidebarMenuItem } from '@umbraco-cms/models'; -import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api'; +import { umbExtensionsRegistry, createExtensionElement } from '@umbraco-cms/extensions-api'; import { UmbLitElement } from '@umbraco-cms/element'; import './section-sidebar-menu/section-sidebar-menu.element.ts'; @@ -90,7 +89,6 @@ export class UmbSectionElement extends UmbLitElement { private _createMenuRoutes() { - console.log("_createMenuRoutes") // TODO: find a way to make this reuseable across: const workspaceRoutes = this._workspaces?.map((workspace: ManifestWorkspace) => { return [ @@ -99,7 +97,7 @@ export class UmbSectionElement extends UmbLitElement { component: () => createExtensionElement(workspace), setup: (component: Promise, info: IRoutingInfo) => { component.then((el) => { - el.entityKey = info.match.params.key; + el.load(info.match.params.key); }); }, }, @@ -108,7 +106,7 @@ export class UmbSectionElement extends UmbLitElement { component: () => createExtensionElement(workspace), setup: (component: Promise) => { component.then((el) => { - el.create = null; + el.create(null); }); }, }, @@ -117,7 +115,7 @@ export class UmbSectionElement extends UmbLitElement { component: () => createExtensionElement(workspace), setup: (component: Promise, info: IRoutingInfo) => { component.then((el) => { - el.create = info.match.params.parentKey; + el.create(info.match.params.parentKey); }); }, }, @@ -170,8 +168,6 @@ export class UmbSectionElement extends UmbLitElement { private _createViewRoutes() { - console.log("_createViewRoutes") - this._routes = this._views?.map((view) => { return { 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 e86656b85c..3d2d66f0d0 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 @@ -2,8 +2,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { css, html, nothing } from 'lit'; 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 { UmbWorkspaceEntityContextInterface } from '../workspace/workspace-context/workspace-entity-context.interface'; import { UmbLitElement } from '@umbraco-cms/element'; import type { ContentTreeItem } from '@umbraco-cms/backend-api'; @@ -45,13 +44,13 @@ export class UmbVariantSelectorElement extends UmbLitElement { @state() _content?: ContentTreeItem; - private _workspaceContext?: UmbWorkspaceContentContext; + private _workspaceContext?: UmbWorkspaceEntityContextInterface; constructor() { super(); // TODO: Figure out how to get the magic string for the workspace context. - this.consumeContext( + this.consumeContext>( 'umbWorkspaceContext', (instance) => { this._workspaceContext = instance; @@ -63,8 +62,10 @@ export class UmbVariantSelectorElement extends UmbLitElement { private async _observeWorkspace() { if (!this._workspaceContext) return; - this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (data) => { - this._content = data; + this.observe(this._workspaceContext.data, (data) => { + if(data) { + this._content = data; + } }); } @@ -74,7 +75,8 @@ export class UmbVariantSelectorElement extends UmbLitElement { const target = event.composedPath()[0] as UUIInputElement; if (typeof target?.value === 'string') { - this._workspaceContext?.update({ name: target.value }); + // TODO: create a setName method on EntityWorkspace: + //this._workspaceContext?.update({ name: target.value }); } } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace-property/workspace-property.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace-property/workspace-property.context.ts index c5553a5d74..f3581b8247 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace-property/workspace-property.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace-property/workspace-property.context.ts @@ -1,4 +1,4 @@ -import { UmbWorkspaceContentContext } from '../workspace/workspace-content/workspace-content.context'; +import { UmbWorkspaceEntityContextInterface } from '../workspace/workspace-context/workspace-entity-context.interface'; import type { DataTypeDetails } from '@umbraco-cms/models'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { createObservablePart, ObjectState } from '@umbraco-cms/observable-api'; @@ -24,11 +24,11 @@ export class UmbWorkspacePropertyContext { public readonly value = createObservablePart(this._data, (data) => data.value); public readonly config = createObservablePart(this._data, (data) => data.config); - private _workspaceContext?: UmbWorkspaceContentContext; + private _workspaceContext?: UmbWorkspaceEntityContextInterface; constructor(host: UmbControllerHostInterface) { // TODO: Figure out how to get the magic string in a better way. - new UmbContextConsumerController(host, 'umbWorkspaceContext', (workspaceContext) => { + new UmbContextConsumerController(host, 'umbWorkspaceContext', (workspaceContext) => { this._workspaceContext = workspaceContext; }); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace-property/workspace-property.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace-property/workspace-property.element.ts index f1059664b9..7e4562b0e7 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace-property/workspace-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace-property/workspace-property.element.ts @@ -3,8 +3,7 @@ import { css, html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { ifDefined } from 'lit-html/directives/if-defined.js'; import { UmbWorkspacePropertyContext } from './workspace-property.context'; -import { createExtensionElement } from '@umbraco-cms/extensions-api'; -import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api'; +import { createExtensionElement , umbExtensionsRegistry } from '@umbraco-cms/extensions-api'; import type { DataTypePropertyData, ManifestPropertyEditorUI, ManifestTypes } from '@umbraco-cms/models'; import '../../property-actions/shared/property-action-menu/property-action-menu.element'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/actions/save/workspace-action-node-save.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/actions/save/workspace-action-node-save.element.ts index 733eb1d661..29fac1453f 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/actions/save/workspace-action-node-save.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/actions/save/workspace-action-node-save.element.ts @@ -2,7 +2,7 @@ import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import type { UUIButtonState } from '@umbraco-ui/uui'; -import { UmbWorkspaceContentContext } from '../../workspace-content/workspace-content.context'; +import { UmbWorkspaceEntityContextInterface } from '../../workspace-context/workspace-entity-context.interface'; import { UmbLitElement } from '@umbraco-cms/element'; import type { ManifestWorkspaceAction } from '@umbraco-cms/models'; @@ -13,7 +13,7 @@ export class UmbWorkspaceActionNodeSaveElement extends UmbLitElement { @state() private _saveButtonState?: UUIButtonState; - private _workspaceContext?: UmbWorkspaceContentContext; + private _workspaceContext?: UmbWorkspaceEntityContextInterface; public manifest?: ManifestWorkspaceAction; @@ -21,7 +21,7 @@ export class UmbWorkspaceActionNodeSaveElement extends UmbLitElement { super(); // TODO: Figure out how to get the magic string for the workspace context. - this.consumeContext('umbWorkspaceContext', (instance) => { + this.consumeContext('umbWorkspaceContext', (instance) => { this._workspaceContext = instance; }); } 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 8a09c6e2af..d4f1646864 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 @@ -2,7 +2,6 @@ import { css, html } from 'lit'; 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, @@ -11,6 +10,7 @@ import { import '../../../../../../shared/components/content-property/content-property.element'; import '../../../../../../shared/collection/dashboards/dashboard-collection.element'; +import { UmbWorkspaceEntityContextInterface } from '../../../workspace-context/workspace-entity-context.interface'; import { UmbLitElement } from '@umbraco-cms/element'; import { FolderTreeItem } from '@umbraco-cms/backend-api'; import { ManifestWorkspaceViewCollection } from '@umbraco-cms/extensions-registry'; @@ -29,7 +29,7 @@ export class UmbWorkspaceViewCollectionElement extends UmbLitElement { public manifest!: ManifestWorkspaceViewCollection; - private _workspaceContext?: UmbWorkspaceContentContext; + private _workspaceContext?: UmbWorkspaceEntityContextInterface; private _collectionContext?: UmbCollectionContext; @@ -37,20 +37,21 @@ export class UmbWorkspaceViewCollectionElement extends UmbLitElement { super(); // TODO: Figure out how to get the magic string for the workspace context. - this.consumeContext('umbWorkspaceContext', (nodeContext) => { + this.consumeContext('umbWorkspaceContext', (nodeContext) => { this._workspaceContext = nodeContext; this._provideWorkspace(); }); } protected _provideWorkspace() { - if (this._workspaceContext?.entityKey != null) { + const entityKey = this._workspaceContext?.getEntityKey(); + if (entityKey != null) { const manifestMeta = this.manifest.meta; this._collectionContext = new UmbCollectionContext( this, - this._workspaceContext.entityKey, + entityKey, manifestMeta.storeAlias ); this.provideContext(UMB_COLLECTION_CONTEXT_TOKEN, this._collectionContext); @@ -58,7 +59,7 @@ export class UmbWorkspaceViewCollectionElement extends UmbLitElement { } render() { - return html``; + return html``; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/edit/workspace-view-content-edit.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/edit/workspace-view-content-edit.element.ts index d10cf56215..12cc9731e9 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/edit/workspace-view-content-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/edit/workspace-view-content-edit.element.ts @@ -3,7 +3,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, state } from 'lit/decorators.js'; import { distinctUntilChanged } from 'rxjs'; import { repeat } from 'lit/directives/repeat.js'; -import { UmbWorkspaceContentContext } from '../../workspace-content.context'; +import type { UmbWorkspaceEntityContextInterface } from '../../../workspace-context/workspace-entity-context.interface'; import type { ContentProperty, ContentPropertyData, DocumentDetails, MediaDetails } from '@umbraco-cms/models'; import '../../../../content-property/content-property.element'; @@ -27,13 +27,13 @@ export class UmbWorkspaceViewContentEditElement extends UmbLitElement { @state() _data: ContentPropertyData[] = []; - private _workspaceContext?: UmbWorkspaceContentContext; + private _workspaceContext?: UmbWorkspaceEntityContextInterface; constructor() { super(); // TODO: Figure out how to get the magic string for the workspace context. - this.consumeContext>( + this.consumeContext>( 'umbWorkspaceContext', (workspaceContext) => { this._workspaceContext = workspaceContext; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/info/workspace-view-content-info.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/info/workspace-view-content-info.element.ts index ae148d8937..8db59d9664 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/info/workspace-view-content-info.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/info/workspace-view-content-info.element.ts @@ -1,7 +1,7 @@ import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, state } from 'lit/decorators.js'; -import type { UmbWorkspaceContentContext } from '../../workspace-content.context'; +import { UmbWorkspaceEntityContextInterface } from '../../../workspace-context/workspace-entity-context.interface'; import type { DocumentDetails, MediaDetails } from '@umbraco-cms/models'; import { UmbLitElement } from '@umbraco-cms/element'; @@ -20,13 +20,13 @@ export class UmbWorkspaceViewContentInfoElement extends UmbLitElement { @state() private _nodeName = ''; - private _workspaceContext?: UmbWorkspaceContentContext; + private _workspaceContext?: UmbWorkspaceEntityContextInterface; constructor() { super(); // TODO: Figure out how to get the magic string for the workspace context. - this.consumeContext>( + this.consumeContext>( 'umbWorkspaceContext', (nodeContext) => { this._workspaceContext = nodeContext; 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 deleted file mode 100644 index b55c24255e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/workspace-content.context.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { v4 as uuidv4 } from 'uuid'; -import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, UmbNotificationDefaultData } from '@umbraco-cms/notification'; -import { UmbControllerHostInterface } from '@umbraco-cms/controller'; -import { UmbContextConsumerController, UmbContextProviderController } from '@umbraco-cms/context-api'; -import { ObjectState, UmbObserverController, createObservablePart } from '@umbraco-cms/observable-api'; -import { UmbContentStore } from '@umbraco-cms/store'; -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 ContentTreeItem = ContentTreeItem, - StoreType extends UmbContentStore = UmbContentStore -> { - protected _host: UmbControllerHostInterface; - - // TODO: figure out how fine grained we want to make our observables. - // TODO: add interface - protected _data; - public readonly data; - public readonly name; - - protected _notificationService?: UmbNotificationService; - - protected _store: StoreType | null = null; - protected _storeSubscription?: UmbObserverController; - - #isNew = true; - - public entityKey?: string; - public entityType: string; - - constructor(host: UmbControllerHostInterface, defaultData: ContentTypeType, storeAlias: string, entityType: string) { - this._host = host; - - this._data = new ObjectState(defaultData); - this.data = this._data.asObservable(); - this.name = createObservablePart(this._data, (data) => data.name); - - this.entityType = entityType; - - new UmbContextConsumerController(host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (_instance) => { - this._notificationService = _instance; - }); - - new UmbContextConsumerController(host, storeAlias, (_instance: StoreType) => { - this._store = _instance; - if (!this._store) { - // TODO: make sure to break the application in a good way. - return; - } - this._observeStore(); - - // TODO: first provide when we have umbNotificationService as well. - new UmbContextProviderController(this._host, 'umbWorkspaceContext', this); - }); - } - - public getData() { - return this._data.getValue(); - } - public update(data: Partial) { - this._data.next({ ...this.getData(), ...data }); - } - - load(entityKey: string) { - this.#isNew = false; - this.entityKey = entityKey; - this._observeStore(); - } - - create(parentKey: string | null) { - this.#isNew = true; - this.entityKey = uuidv4(); - console.log("I'm new, and I will be created under ", parentKey); - } - - protected _observeStore(): void { - if (!this._store || !this.entityKey) { - return; - } - - if (!this.#isNew) { - this._storeSubscription?.destroy(); - this._storeSubscription = new UmbObserverController( - this._host, - this._store.getByKey(this.entityKey), - (content) => { - if (!content) return; // TODO: Handle nicely if there is no content data. - this.update(content as any); - } - ); - } - } - - public getStore() { - return this._store; - } - - abstract setPropertyValue(alias: string, value: unknown): void; - - // TODO: consider turning this into an abstract so each context implement this them selfs. - public save(): Promise { - if (!this._store) { - // TODO: add a more beautiful error: - console.error('Could not save cause workspace context has no store.'); - return Promise.resolve(); - } - return this._store - .save([this.getData()]) - .then(() => { - const data: UmbNotificationDefaultData = { message: 'Document Saved' }; - this._notificationService?.peek('positive', { data }); - }) - .catch(() => { - const data: UmbNotificationDefaultData = { message: 'Failed to save Document' }; - this._notificationService?.peek('danger', { data }); - }); - } - - // 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(); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/entity-manager-controller.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/entity-manager-controller.ts new file mode 100644 index 0000000000..6259693f7b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/entity-manager-controller.ts @@ -0,0 +1,133 @@ +import { v4 as uuidv4 } from 'uuid'; +import { UmbContextConsumerController, UmbContextToken } from "@umbraco-cms/context-api"; +import { UmbControllerHostInterface } from "@umbraco-cms/controller"; +import { UmbNotificationDefaultData, UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from "@umbraco-cms/notification"; +import { ObjectState, UmbObserverController } from "@umbraco-cms/observable-api"; +import { EntityTreeItem } from '@umbraco-cms/backend-api'; +import { UmbEntityDetailStore } from '@umbraco-cms/store'; + + +// Extend entityType base type?, so we are sure to have parentKey? +// TODO: switch to use EntityDetailItem ? if we can have such type? +export class UmbEntityWorkspaceManager, EntityDetailsType extends EntityTreeItem = ReturnType> { + + + private _host; + + state = new ObjectState(undefined); + + + + protected _storeSubscription?: UmbObserverController; + + private _notificationService?: UmbNotificationService; + private _store?: StoreType; + + + #isNew = false; + private _entityType; + private _entityKey!: string; + + private _createAtParentKey?: string | null; + + + constructor( + host: UmbControllerHostInterface, + entityType:string, + storeToken: UmbContextToken + ) { + this._host = host; + this._entityType = entityType; + + new UmbContextConsumerController(this._host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (_instance) => { + this._notificationService = _instance; + }); + + // Create controller holding Token? + new UmbContextConsumerController(this._host, storeToken, (_instance) => { + this._store = _instance; + this._observeStore(); + }); + } + + private _observeStore() { + if (!this._store || !this._entityKey) { + return; + } + + if (this.#isNew) { + const newData = this._store.getScaffold(this._entityType, this._createAtParentKey || null); + this.state.next(newData); + } else { + this._storeSubscription?.destroy(); + this._storeSubscription = new UmbObserverController( + this._host, + this._store.getByKey(this._entityKey), + (content) => { + if (!content) return; // TODO: Handle nicely if there is no content data. + this.state.next(content as any); + } + ); + } + } + + getEntityType() { + return this._entityType; + } + getEntityKey(): string { + return this._entityKey; + } + + getStore() { + return this._store; + } + + getData() { + return this.state.getValue(); + } + + load(entityKey: string) { + this.#isNew = false; + this._entityKey = entityKey; + this._observeStore(); + } + + create(parentKey: string | null) { + this.#isNew = true; + this._entityKey = uuidv4(); + this._createAtParentKey = parentKey; + } + + save(): Promise { + + if (!this._store) { + // TODO: add a more beautiful error: + console.error('Could not save cause workspace context has no store.'); + return Promise.resolve(); + } + + const documentData = this.getData(); + if(!documentData) { + console.error('Could not save cause workspace context has no data.'); + return Promise.resolve(); + } + + return this._store + .save([documentData]) + .then(() => { + const data: UmbNotificationDefaultData = { message: 'Document Saved' }; + this._notificationService?.peek('positive', { data }); + }) + .catch(() => { + const data: UmbNotificationDefaultData = { message: 'Failed to save Document' }; + this._notificationService?.peek('danger', { data }); + }); + } + + + public destroy(): void { + this.state.unsubscribe(); + } + + +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.interface.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.interface.ts new file mode 100644 index 0000000000..f5b370f707 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.interface.ts @@ -0,0 +1,16 @@ +import type { Observable } from "rxjs"; + +export interface UmbWorkspaceContextInterface { + + readonly data: Observable; + + getUnique(): string | undefined; + + getData(): T; + + load(unique: string): void; + + create(parentUnique: string | null): void; + + destroy(): void; +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.ts new file mode 100644 index 0000000000..9740db85c7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-context.ts @@ -0,0 +1,14 @@ +import { UmbContextProviderController } from '@umbraco-cms/context-api'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; + + +export abstract class UmbWorkspaceContext { + + protected _host: UmbControllerHostInterface; + + constructor(host: UmbControllerHostInterface) { + this._host = host; + new UmbContextProviderController(host, 'UmbWorkspaceContext', this); + } + +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-entity-context.interface.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-entity-context.interface.ts new file mode 100644 index 0000000000..f513ee751f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-context/workspace-entity-context.interface.ts @@ -0,0 +1,20 @@ +import type { Observable } from "rxjs"; +import { UmbWorkspaceContextInterface } from "./workspace-context.interface"; +import { UmbEntityDetailStore } from "@umbraco-cms/store"; + +export interface UmbWorkspaceEntityContextInterface extends UmbWorkspaceContextInterface { + + readonly name: Observable; + + getEntityKey(): string | undefined;// COnsider if this should go away now that we have getUnique() + getEntityType(): string; + + getData(): T; + + getStore(): UmbEntityDetailStore | undefined; + + setPropertyValue(alias: string, value: unknown): void; + + save(): Promise; + +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-entity-element.interface.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-entity-element.interface.ts index 5b56617df4..f701853c32 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-entity-element.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-entity-element.interface.ts @@ -1,4 +1,4 @@ export interface UmbWorkspaceEntityElement { - set entityKey(key: string); - set create(parentKey: string | null); + load(key: string): void; + create(parentKey: string | null): void; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.element.ts index 2f2bce50dc..5ca1c0dbcb 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.element.ts @@ -4,8 +4,7 @@ import { customElement, property, state } from 'lit/decorators.js'; import { IRoutingInfo, RouterSlot } from 'router-slot'; import { map } from 'rxjs'; -import { createExtensionElement } from '@umbraco-cms/extensions-api'; -import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api'; +import { createExtensionElement , umbExtensionsRegistry } from '@umbraco-cms/extensions-api'; import type { ManifestWorkspaceAction, ManifestWorkspaceView, diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/textarea/property-editor-ui-textarea.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/textarea/property-editor-ui-textarea.element.ts index df1980458f..ba7c1245cf 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/textarea/property-editor-ui-textarea.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/textarea/property-editor-ui-textarea.element.ts @@ -1,9 +1,9 @@ import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, property } from 'lit/decorators.js'; +import { UUITextareaElement } from '@umbraco-ui/uui'; import type { UmbWorkspacePropertyContext } from 'src/backoffice/shared/components/workspace-property/workspace-property.context'; import { UmbLitElement } from '@umbraco-cms/element'; -import { UUITextareaElement } from '@umbraco-ui/uui'; @customElement('umb-property-editor-ui-textarea') export class UmbPropertyEditorUITextareaElement extends UmbLitElement { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/themes/theme.service.ts b/src/Umbraco.Web.UI.Client/src/backoffice/themes/theme.service.ts new file mode 100644 index 0000000000..d56b4d57a7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/themes/theme.service.ts @@ -0,0 +1,55 @@ +import { dark, highContrast } from './themes'; +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { ArrayState, StringState } from '@umbraco-cms/observable-api'; + +export interface UmbTheme { + name: string; + css: string; +} + +export class UmbThemeService { + + // TODO: Turn this into a extension type, get rid of the #themes subject and #themes observable + #themes = new ArrayState(>[ + { + name: 'Light', + css: '', + }, + ]); + public readonly themes = this.#themes.asObservable(); + + #theme = new StringState('Light'); + public readonly theme = this.#theme.asObservable(); + + #styleElement: HTMLStyleElement; + + constructor() { + //TODO: Figure out how to extend this with themes from packages + this.addTheme(dark); + this.addTheme(highContrast); + + this.#styleElement = document.createElement('style'); + const storedTheme = localStorage.getItem('umb-theme'); + this.changeTheme(storedTheme ?? this.#theme.value); + + document.documentElement.insertAdjacentElement('beforeend', this.#styleElement); + } + + public changeTheme(theme: string) { + this.#theme.next(theme); + localStorage.setItem('umb-theme', theme); + + // TODO: This should come from the extension API: + const themeCss = this.#themes.value.find((t) => t.name === theme)?.css; + + if (themeCss !== undefined) { + this.#styleElement.innerHTML = themeCss; + } + } + + public addTheme(theme: UmbTheme) { + this.#themes.next([...this.#themes.value, theme]); + } +} + +export const UMB_THEME_SERVICE_CONTEXT_TOKEN = new UmbContextToken(UmbThemeService.name); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/themes/themes/dark.theme.ts b/src/Umbraco.Web.UI.Client/src/backoffice/themes/themes/dark.theme.ts new file mode 100644 index 0000000000..205d01ec14 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/themes/themes/dark.theme.ts @@ -0,0 +1,59 @@ +import { css } from 'lit'; +import { UmbTheme } from '../theme.service'; + +// TODO: We should get this from UUI, and it should be served through an extension. +const name = 'Dark'; +const cssResult = css` + :root { + --uui-color-selected: #316dca; + --uui-color-selected-emphasis: #3e79d0; + --uui-color-selected-standalone: #5b8dd7; + --uui-color-selected-contrast: #eeeeef; + --uui-color-current: #316dca; + --uui-color-current-emphasis: #3e79d0; + --uui-color-current-standalone: #5b8dd7; + --uui-color-current-contrast: #f000; + --uui-color-disabled: #434c56; + --uui-color-disabled-standalone: #545d68; + --uui-color-disabled-contrast: #fcfcfc4d; + --uui-color-header-surface: #21262e; + --uui-color-header-contrast: #eeeeef; + --uui-color-header-contrast-emphasis: #eeeeef; + --uui-color-focus: #316dca; + --uui-color-surface: #2d333b; + --uui-color-surface-alt: #373e47; + --uui-color-surface-emphasis: #434c56; + --uui-color-background: #21262e; + --uui-color-text: #eeeeef; + --uui-color-text-alt: #eeeeef; + --uui-color-interactive: #eeeeef; + --uui-color-interactive-emphasis: #eeeeef; + --uui-color-border: #434c56; + --uui-color-border-standalone: #545d68; + --uui-color-border-emphasis: #626e7b; + --uui-color-divider: #373e47; + --uui-color-divider-standalone: #434c56; + --uui-color-divider-emphasis: #545d68; + --uui-color-default: #316dca; + --uui-color-default-emphasis: #316dca; + --uui-color-default-standalone: #316dca; + --uui-color-default-contrast: #eeeeef; + --uui-color-warning: #af7c12; + --uui-color-warning-emphasis: #af7c12; + --uui-color-warning-standalone: #af7c12; + --uui-color-warning-contrast: #eeeeef; + --uui-color-danger: #ca3b37; + --uui-color-danger-emphasis: #ca3b37; + --uui-color-danger-standalone: #ca3b37; + --uui-color-danger-contrast: #eeeeef; + --uui-color-positive: #347d39; + --uui-color-positive-emphasis: #347d39; + --uui-color-positive-standalone: #347d39; + --uui-color-positive-contrast: #eeeeef; + } +`; + +export const dark: UmbTheme = { + name: name, + css: cssResult.cssText, +}; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/themes/themes/high-contrast.theme.ts b/src/Umbraco.Web.UI.Client/src/backoffice/themes/themes/high-contrast.theme.ts new file mode 100644 index 0000000000..d7b1090c91 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/themes/themes/high-contrast.theme.ts @@ -0,0 +1,65 @@ +import { css } from 'lit'; +import { UmbTheme } from '../theme.service'; + +// TODO: We should get this from UUI, and it should be served through an extension. +const name = 'High Contrast'; +const cssResult = css` + :root { + --uui-color-selected: var(--uui-palette-violet-blue, #3544b1); + --uui-color-selected-emphasis: var(--uui-palette-violet-blue-light, rgb(70, 86, 200)); + --uui-color-selected-standalone: var(--uui-palette-violet-blue-dark, rgb(54, 65, 156)); + --uui-color-selected-contrast: #fff; + --uui-color-current: var(--uui-palette-spanish-pink, #f5c1bc); + --uui-color-current-emphasis: var(--uui-palette-spanish-pink-light, rgb(248, 214, 211)); + --uui-color-current-standalone: var(--uui-palette-spanish-pink-dark, rgb(232, 192, 189)); + --uui-color-current-contrast: var(--uui-palette-space-cadet, #1b264f); + --uui-color-disabled: var(--uui-palette-sand, #f3f3f5); + --uui-color-disabled-standalone: var(--uui-palette-sand-dark, rgb(226, 226, 226)); + --uui-color-disabled-contrast: var(--uui-palette-grey, #c4c4c4); + --uui-color-header-surface: var(--uui-palette-space-cadet, #1b264f); + --uui-color-header-contrast: #fff; + --uui-color-header-contrast-emphasis: #fff; + --uui-color-focus: var(--uui-palette-malibu, #3879ff); + --uui-color-surface: #fff; + --uui-color-surface-alt: #fff; + --uui-color-surface-emphasis: #dadada; + --uui-color-background: #fff; + --uui-color-text: var(--uui-palette-black, #060606); + --uui-color-text-alt: var(--uui-palette-dune-black, #2e2b29); + --uui-color-interactive: var(--uui-palette-space-cadet, #1b264f); + --uui-color-interactive-emphasis: var(--uui-palette-violet-blue, #3544b1); + --uui-color-border: #000000; + --uui-color-border-standalone: #000000; + --uui-color-border-emphasis: #000000; + --uui-color-divider: #000000; + --uui-color-divider-standalone: #000000; + --uui-color-divider-emphasis: #000000; + --uui-color-default: var(--uui-palette-space-cadet, #1b264f); + --uui-color-default-emphasis: var(--uui-palette-violet-blue, #3544b1); + --uui-color-default-standalone: var(--uui-palette-space-cadet-dark, rgb(28, 35, 59)); + --uui-color-default-contrast: #fff; + --uui-color-warning: #ffd621; + --uui-color-warning-emphasis: #ffdc41; + --uui-color-warning-standalone: #ffdd43; + --uui-color-warning-contrast: #000; + --uui-color-danger: #c60239; + --uui-color-danger-emphasis: #da114a; + --uui-color-danger-standalone: #d0003b; + --uui-color-danger-contrast: white; + --uui-color-positive: #0d8844; + --uui-color-positive-emphasis: #159c52; + --uui-color-positive-standalone: #1cae5e; + --uui-color-positive-contrast: #fff; + + --uui-shadow-depth-1: 0 0 0px 1px black; + --uui-shadow-depth-2: 0 0 0px 1px black; + --uui-shadow-depth-3: 0 0 0px 1px black; + --uui-shadow-depth-4: 0 0 0px 1px black; + --uui-shadow-depth-5: 0 0 0px 1px black; + } +`; + +export const highContrast: UmbTheme = { + name: name, + css: cssResult.cssText, +}; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/themes/themes/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/themes/themes/index.ts new file mode 100644 index 0000000000..cbee9b178f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/themes/themes/index.ts @@ -0,0 +1,2 @@ +export * from './dark.theme'; +export * from './high-contrast.theme'; 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 index e22c529d0d..88ba424035 100644 --- 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 @@ -1,7 +1,7 @@ import type { DictionaryDetails } from '@umbraco-cms/models'; import { UmbContextToken } from '@umbraco-cms/context-api'; import { createObservablePart, ArrayState } from '@umbraco-cms/observable-api'; -import { UmbStoreBase } from '@umbraco-cms/store'; +import { UmbEntityDetailStore, UmbStoreBase } from '@umbraco-cms/store'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { EntityTreeItem } from '@umbraco-cms/backend-api'; @@ -15,7 +15,8 @@ export const UMB_DICTIONARY_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken { // TODO: use the right type: @@ -26,6 +27,12 @@ export class UmbDictionaryDetailStore extends UmbStoreBase { super(host, UMB_DICTIONARY_DETAIL_STORE_CONTEXT_TOKEN.toString()); } + + getScaffold(entityType: string, parentKey: string | null) { + return { + } as EntityTreeItem; + } + /** * @description - Request a Data Type by key. The Data Type is added to the store and is returned as an Observable. * @param {string} key diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/current-user.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/current-user.store.ts index 02f701b2ec..6f6dcdc54e 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/current-user.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/current-user.store.ts @@ -1,11 +1,12 @@ +import { umbUsersData } from '../../../core/mocks/data/users.data'; import { umbracoPath } from '@umbraco-cms/utils'; import type { UserDetails } from '@umbraco-cms/models'; import { UmbContextToken } from '@umbraco-cms/context-api'; import { ObjectState } from '@umbraco-cms/observable-api'; export class UmbCurrentUserStore { - - private _currentUser = new ObjectState(undefined); + //TODO: Temp solution to get a current user. Replace when we have a real user service + private _currentUser = new ObjectState(umbUsersData.getAll()[0]); public readonly currentUser = this._currentUser.asObservable(); /** diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/manifests.ts index 5cf2fcce1a..63d58c00a4 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/manifests.ts @@ -3,13 +3,13 @@ import type { ManifestHeaderApp, ManifestUserDashboard } from '@umbraco-cms/mode export const userDashboards: Array = [ { type: 'userDashboard', - alias: 'Umb.UserDashboard.Test', - name: 'Test User Dashboard', - loader: () => import('./user-dashboard-test.element'), - weight: 2, + alias: 'Umb.UserDashboard.Themes', + name: 'Themes User Dashboard', + loader: () => import('./user-dashboard-themes.element'), + weight: 1, meta: { - label: 'Test User Dashboard', - pathname: 'test/test/test', + label: 'Themes User Dashboard', + pathname: 'themes', }, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/user-dashboard-test.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/user-dashboard-test.element.ts deleted file mode 100644 index e2db3b031e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/user-dashboard-test.element.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { css, html } from 'lit'; -import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { customElement } from 'lit/decorators.js'; -import { UmbLitElement } from '@umbraco-cms/element'; - -@customElement('umb-user-dashboard-test') -export class UmbUserDashboardTestElement extends UmbLitElement { - static styles = [ - UUITextStyles, - css` - :host { - display: flex; - flex-direction: column; - gap: var(--uui-size-space-4); - padding: var(--uui-size-space-5); - border: 1px solid var(--uui-color-border); - background: var(--uui-color-positive); - color: var(--uui-color-positive-contrast); - border-radius: var(--uui-border-radius); - } - p { - margin: 0; - } - `, - ]; - - render() { - return html` - Custom User Dashboard -

This is an example of a custom user dashboard using the user dashboard extension point

- `; - } -} - -export default UmbUserDashboardTestElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-user-dashboard-test': UmbUserDashboardTestElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/user-dashboard-themes.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/user-dashboard-themes.element.ts new file mode 100644 index 0000000000..ffe608c698 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/user-dashboard-themes.element.ts @@ -0,0 +1,78 @@ +import { css, html } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, state } from 'lit/decorators.js'; +import { UUISelectEvent } from '@umbraco-ui/uui'; +import { UmbThemeService, UMB_THEME_SERVICE_CONTEXT_TOKEN } from '../../themes/theme.service'; +import { UmbLitElement } from '@umbraco-cms/element'; + +@customElement('umb-user-dashboard-test') +export class UmbUserDashboardTestElement extends UmbLitElement { + static styles = [ + UUITextStyles, + css` + :host { + display: flex; + flex-direction: column; + gap: var(--uui-size-space-4); + padding: var(--uui-size-space-5); + border: 1px solid var(--uui-color-border); + background: var(--uui-color-surface); + color: var(--uui-color-text); + border-radius: var(--uui-border-radius); + } + `, + ]; + + #themeService?: UmbThemeService; + + @state() + private _theme = ''; + + @state() + private _themes: Array = []; + + constructor() { + super(); + this.consumeContext(UMB_THEME_SERVICE_CONTEXT_TOKEN, (instance) => { + this.#themeService = instance; + instance.theme.subscribe((theme) => { + this._theme = theme; + }); + // TODO: We should get rid of the #themes state and instead use an extension point: + instance.themes.subscribe((themes) => { + this._themes = themes.map((t) => t.name); + }); + }); + } + + private _handleThemeChange(event: UUISelectEvent) { + if (!this.#themeService) return; + + const theme = event.target.value.toString(); + + this.#themeService.changeTheme(theme); + } + + get options() { + return this._themes.map((t) => ({ name: t, value: t, selected: t === this._theme })); + } + + render() { + return html` + Select Theme + + `; + } +} + +export default UmbUserDashboardTestElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-user-dashboard-test': UmbUserDashboardTestElement; + } +} 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 31f3b99a22..b5ac06137a 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 @@ -2,10 +2,10 @@ import type { UserGroupDetails } from '@umbraco-cms/models'; import { UmbContextToken } from '@umbraco-cms/context-api'; import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { createObservablePart, ArrayState } from '@umbraco-cms/observable-api'; -import { UmbStoreBase } from '@umbraco-cms/store'; +import { UmbEntityDetailStore, UmbStoreBase } from '@umbraco-cms/store'; // TODO: get rid of this type addition & { ... }: -export type UmbUserGroupStoreItemType = UserGroupDetails & { users?: Array }; +//export type UmbUserGroupStoreItemType = UserGroupDetails & { users?: Array }; export const UMB_USER_GROUP_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbUserGroupStore'); @@ -15,10 +15,10 @@ export const UMB_USER_GROUP_STORE_CONTEXT_TOKEN = new UmbContextToken { - #groups = new ArrayState([], x => x.key); + #groups = new ArrayState([], x => x.key); public groups = this.#groups.asObservable(); @@ -26,6 +26,22 @@ export class UmbUserGroupStore extends UmbStoreBase { super(host, UMB_USER_GROUP_STORE_CONTEXT_TOKEN.toString()); } + + + getScaffold(entityType: string, parentKey: string | null) { + return { + key: '', + name: '', + icon: '', + type: 'user-group', + hasChildren: false, + parentKey: '', + sections: [], + permissions: [], + users: [], + } as UserGroupDetails; + } + getAll() { // TODO: use Fetcher API. // TODO: only fetch if the data type is not in the store? @@ -61,10 +77,10 @@ export class UmbUserGroupStore extends UmbStoreBase { return createObservablePart(this.groups, (items) => items.filter(node => keys.includes(node.key))); } - async save(userGroups: Array) { + async save(userGroups: Array) { // TODO: use Fetcher API. - // TODO: implement so user group store updates the + // TODO: implement so user group store updates the users, but these needs to save as well..? /* if (this._userStore && userGroup.users) { await this._userStore.updateUserGroup(userGroup.users, userGroup.key); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.context.ts index a7168973a9..470163bc3c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.context.ts @@ -1,27 +1,31 @@ -import { UmbWorkspaceContentContext } from '../../../shared/components/workspace/workspace-content/workspace-content.context'; -import { UMB_USER_STORE_CONTEXT_TOKEN } from '../../users/user.store'; -import type { UmbUserGroupStore, UmbUserGroupStoreItemType } from 'src/backoffice/users/user-groups/user-group.store'; -import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { UmbEntityWorkspaceManager } from '../../../shared/components/workspace/workspace-context/entity-manager-controller'; +import { UmbWorkspaceContext } from '../../../shared/components/workspace/workspace-context/workspace-context'; +import { UmbWorkspaceEntityContextInterface } from '../../../shared/components/workspace/workspace-context/workspace-entity-context.interface'; +import { UMB_USER_GROUP_STORE_CONTEXT_TOKEN } from '../user-group.store'; +import type { UserGroupDetails } from '@umbraco-cms/models'; -const DefaultDataTypeData = { - key: '', - name: '', - icon: '', - type: 'user-group', - hasChildren: false, - parentKey: '', - sections: [], - permissions: [], - users: [], -} as UmbUserGroupStoreItemType; -export class UmbWorkspaceUserGroupContext extends UmbWorkspaceContentContext< - UmbUserGroupStoreItemType, - UmbUserGroupStore -> { - constructor(host: UmbControllerHostInterface) { - super(host, DefaultDataTypeData, UMB_USER_STORE_CONTEXT_TOKEN.toString(), 'userGroup'); +export class UmbWorkspaceUserGroupContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { + + + + #manager = new UmbEntityWorkspaceManager(this._host, 'user-group', UMB_USER_GROUP_STORE_CONTEXT_TOKEN); + + public readonly data = this.#manager.state.asObservable(); + public readonly name = this.#manager.state.getObservablePart((state) => state?.name); + + setName(name: string) { + this.#manager.state.update({name: name}) } + getEntityType = this.#manager.getEntityType; + getUnique = this.#manager.getEntityKey; + getEntityKey = this.#manager.getEntityKey; + getStore = this.#manager.getStore; + getData = this.#manager.getData; + load = this.#manager.load; + create = this.#manager.create; + save = this.#manager.save; + destroy = this.#manager.destroy; public setPropertyValue(alias: string, value: unknown) { throw new Error('setPropertyValue is not implemented for UmbWorkspaceUserGroupContext'); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.element.ts index 12af723b84..fea60878ce 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.element.ts @@ -1,7 +1,7 @@ import { UUIInputElement, UUIInputEvent } from '@umbraco-ui/uui'; import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html, nothing } from 'lit'; -import { customElement, property, state } from 'lit/decorators.js'; +import { customElement, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; import { distinctUntilChanged } from 'rxjs'; import { UmbUserStore, UMB_USER_STORE_CONTEXT_TOKEN } from '../../../users/users/user.store'; @@ -186,22 +186,7 @@ export class UmbUserGroupWorkspaceElement extends UmbLitElement implements UmbWo private _userStore?: UmbUserStore; - private _entityKey!: string; - @property() - public get entityKey(): string { - return this._entityKey; - } - public set entityKey(value: string) { - this._entityKey = value; - if (this._entityKey) { - this._workspaceContext.load(this._entityKey); - } - } - @property() - public set create(parentKey: string | null) { - this._workspaceContext.create(parentKey); - } private _workspaceContext: UmbWorkspaceUserGroupContext = new UmbWorkspaceUserGroupContext(this); @@ -226,6 +211,14 @@ export class UmbUserGroupWorkspaceElement extends UmbLitElement implements UmbWo }); } + public load(entityKey: string) { + this._workspaceContext.load(entityKey); + } + + public create(parentKey: string | null) { + this._workspaceContext.create(parentKey); + } + private _registerWorkspaceActions() { const manifests: Array = [ { @@ -255,40 +248,54 @@ export class UmbUserGroupWorkspaceElement extends UmbLitElement implements UmbWo this.observe(this._userStore.getAll(), (users) => { // TODO: handle if there is no users. if (!this._userKeys && users.length > 0) { - this._userKeys = users.filter((user) => user.userGroups.includes(this.entityKey)).map((user) => user.key); - this._updateProperty('users', this._userKeys); + const entityKey = this._workspaceContext.getEntityKey(); + this._userKeys = users.filter((user) => user.userGroups.includes(entityKey)).map((user) => user.key); + //this._updateProperty('users', this._userKeys); + // TODO: make a method on the UmbWorkspaceUserGroupContext: + //this._workspaceContext.setUsers(); } }); } private _updateUserKeys(userKeys: Array) { this._userKeys = userKeys; - this._updateProperty('users', this._userKeys); + // TODO: make a method on the UmbWorkspaceUserGroupContext: + //this._workspaceContext.setUsers(); } - private _updateProperty(propertyName: string, value: unknown) { - this._workspaceContext?.update({ [propertyName]: value }); - } private _updatePermission(permission: { name: string; description: string; value: boolean }) { if (!this._workspaceContext) return; const checkValue = this._checkPermission(permission); - const selectedPermissions = this._workspaceContext.getData().permissions; + //const selectedPermissions = this._workspaceContext.getData().permissions; + // TODO: make a method on the UmbWorkspaceUserGroupContext: + //const selectedPermissions = this._workspaceContext.getPermissions(); + /* let newPermissions = []; if (checkValue === false) { newPermissions = [...selectedPermissions, permission.name]; } else { newPermissions = selectedPermissions.filter((p) => p !== permission.name); } - this._updateProperty('permissions', newPermissions); + */ + + //this._updateProperty('permissions', newPermissions); + // TODO: make a method on the UmbWorkspaceUserGroupContext: + //this._workspaceContext.setPermissions(); } private _checkPermission(permission: { name: string; description: string; value: boolean }) { if (!this._workspaceContext) return false; - return this._workspaceContext.getData().permissions.includes(permission.name); + //return this._workspaceContext.getPermissions().includes(permission.name); + return false; + } + + private _updateSections(value: string[]) { + console.log("To be done"); + //this._workspaceContext.setSections(value); } private renderLeftColumn() { @@ -300,7 +307,7 @@ export class UmbUserGroupWorkspaceElement extends UmbLitElement implements UmbWo this._updateProperty('sections', e.target.value)}> + @change=${(e: any) => this._updateSections(e.target.value)}> ('U * @extends {UmbStoreBase} * @description - Data Store for Users */ -export class UmbUserStore extends UmbStoreBase { +export class UmbUserStore extends UmbStoreBase implements UmbEntityDetailStore { #users = new ArrayState([], x => x.key); @@ -29,6 +29,27 @@ export class UmbUserStore extends UmbStoreBase { } + getScaffold(entityType: string, parentKey: string | null) { + return { + key: '', + name: '', + icon: '', + type: 'user', + hasChildren: false, + parentKey: '', + email: '', + language: '', + status: 'enabled', + updateDate: '8/27/2022', + createDate: '9/19/2022', + failedLoginAttempts: 0, + userGroups: [], + contentStartNodes: [], + mediaStartNodes: [], + } as UserDetails; + } + + getAll() { // TODO: use Fetcher API. // TODO: only fetch if the data type is not in the store? 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 1393c00767..eb22cff826 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 @@ -1,33 +1,31 @@ -import { UmbWorkspaceContentContext } from '../../../shared/components/workspace/workspace-content/workspace-content.context'; -import { - UmbUserStore, - UmbUserStoreItemType, - UMB_USER_STORE_CONTEXT_TOKEN, -} from '../../users/user.store'; -import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { UMB_USER_STORE_CONTEXT_TOKEN } from '../../users/user.store'; +import { UmbWorkspaceContext } from '../../../shared/components/workspace/workspace-context/workspace-context'; +import { UmbWorkspaceEntityContextInterface } from '../../../shared/components/workspace/workspace-context/workspace-entity-context.interface'; +import { UmbEntityWorkspaceManager } from '../../../shared/components/workspace/workspace-context/entity-manager-controller'; +import type { UserDetails } from '@umbraco-cms/models'; -const DefaultDataTypeData = { - key: '', - name: '', - icon: '', - type: 'user', - hasChildren: false, - parentKey: '', - email: '', - language: '', - status: 'enabled', - updateDate: '8/27/2022', - createDate: '9/19/2022', - failedLoginAttempts: 0, - userGroups: [], - contentStartNodes: [], - mediaStartNodes: [], -} as UmbUserStoreItemType; +export class UmbWorkspaceUserContext extends UmbWorkspaceContext implements UmbWorkspaceEntityContextInterface { -export class UmbWorkspaceUserContext extends UmbWorkspaceContentContext { - constructor(host: UmbControllerHostInterface) { - super(host, DefaultDataTypeData, UMB_USER_STORE_CONTEXT_TOKEN.toString(), 'user'); + #manager = new UmbEntityWorkspaceManager(this._host, 'user', UMB_USER_STORE_CONTEXT_TOKEN); + + public readonly data = this.#manager.state.asObservable(); + public readonly name = this.#manager.state.getObservablePart((state) => state?.name); + + // TODO: remove this magic connection, instead create the necessary methods to update parts. + update = this.#manager.state.update; + + setName(name: string) { + this.#manager.state.update({name: name}) } + getEntityType = this.#manager.getEntityType; + getUnique = this.#manager.getEntityKey; + getEntityKey = this.#manager.getEntityKey; + getStore = this.#manager.getStore; + getData = this.#manager.getData; + load = this.#manager.load; + create = this.#manager.create; + save = this.#manager.save; + destroy = this.#manager.destroy; public setPropertyValue(alias: string, value: unknown) { throw new Error('setPropertyValue is not implemented for UmbWorkspaceUserContext'); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.element.ts index 7a469a6ed2..b53c8bf89a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.element.ts @@ -88,22 +88,6 @@ export class UmbUserWorkspaceElement extends UmbLitElement implements UmbWorkspa private _languages = []; //TODO Add languages - private _entityKey!: string; - @property() - public get entityKey(): string { - return this._entityKey; - } - public set entityKey(value: string) { - this._entityKey = value; - if (this._entityKey) { - this._workspaceContext.load(this._entityKey); - } - } - - @property() - public set create(parentKey: string | null) { - this._workspaceContext.create(parentKey); - } private _workspaceContext: UmbWorkspaceUserContext = new UmbWorkspaceUserContext(this); @@ -123,12 +107,20 @@ export class UmbUserWorkspaceElement extends UmbLitElement implements UmbWorkspa this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (user) => { this._user = user; - if (user.name !== this._userName) { + if (user && user.name !== this._userName) { this._userName = user.name; } }); } + public load(entityKey: string) { + this._workspaceContext.load(entityKey); + } + + public create(parentKey: string | null) { + this._workspaceContext.create(parentKey); + } + private async _observeCurrentUser() { if (!this._currentUserStore) return; diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user/picker-layout-user.element.ts b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user/picker-layout-user.element.ts index 126a25fd99..cdbe21b290 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user/picker-layout-user.element.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/layouts/picker-user/picker-layout-user.element.ts @@ -2,8 +2,8 @@ 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 { UmbUserStore, UMB_USER_STORE_CONTEXT_TOKEN } from '../../../../backoffice/users/users/user.store'; import type { UserDetails } from '@umbraco-cms/models'; -import { UmbUserStore, UMB_USER_STORE_CONTEXT_TOKEN } from 'src/backoffice/users/users/user.store'; @customElement('umb-picker-layout-user') export class UmbPickerLayoutUserElement extends UmbModalLayoutPickerBase {