From 76611877fb9b5689321aaa29492ee9f4b8f6f93f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Thu, 16 Nov 2023 16:41:49 +1300 Subject: [PATCH 01/25] cleanup --- .../workspace/media-type-workspace.element.ts | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.element.ts index 6e0191f8cf..c5d3a1f6f9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.element.ts @@ -1,7 +1,7 @@ import { UmbMediaTypeWorkspaceContext } from './media-type-workspace.context.js'; import { UmbMediaTypeWorkspaceEditorElement } from './media-type-workspace-editor.element.js'; -import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; -import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import type { UmbRoute } from '@umbraco-cms/backoffice/router'; @@ -15,7 +15,7 @@ export class UmbMediaTypeWorkspaceElement extends UmbLitElement { { path: 'edit/:id', component: () => this.#element, - setup: (component, info) => { + setup: (_component, info) => { const id = info.match.params.id; this.#workspaceContext.load(id); }, @@ -26,20 +26,7 @@ export class UmbMediaTypeWorkspaceElement extends UmbLitElement { return html``; } - static styles = [ - UmbTextStyles, - css` - #header { - display: flex; - padding: 0 var(--uui-size-layout-1); - gap: var(--uui-size-space-4); - width: 100%; - } - uui-input { - width: 100%; - } - `, - ]; + static styles = [UmbTextStyles]; } export default UmbMediaTypeWorkspaceElement; From dc2336d77d0540e6a4ca8731895253d5594beefa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Thu, 16 Nov 2023 17:23:38 +1300 Subject: [PATCH 02/25] update tree and repositories --- .../src/packages/media/media-types/index.ts | 7 + .../media-types/repository/detail/index.ts | 3 + .../repository/detail/manifests.ts | 22 ++ .../detail/media-type-detail.repository.ts | 179 +++++++++++++ .../media-type-detail.store.ts} | 34 +-- .../detail/media-type.server.data.ts | 147 +++++++++++ .../media/media-types/repository/index.ts | 2 + .../media-types/repository/item/index.ts | 3 + .../media-types/repository/item/manifests.ts | 22 ++ .../item/media-type-item.repository.ts | 11 + .../media-type-item.server.data.ts | 2 +- .../{ => item}/media-type-item.store.ts | 6 +- .../media-types/repository/item/types.ts | 3 + .../media/media-types/repository/manifests.ts | 34 +-- .../repository/media-type.repository.ts | 239 ------------------ .../sources/media-type.detail.server.data.ts | 107 -------- .../media/media-types/tree/manifests.ts | 36 ++- .../tree/media-type-tree.repository.ts | 28 ++ .../media-type.tree.server.data-source.ts} | 14 +- .../media-type.tree.store.ts | 10 +- .../packages/media/media-types/tree/types.ts | 5 + 21 files changed, 498 insertions(+), 416 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts rename src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/{media-type.detail.store.ts => detail/media-type-detail.store.ts} (53%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type.server.data.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.repository.ts rename src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/{sources => item}/media-type-item.server.data.ts (96%) rename src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/{ => item}/media-type-item.store.ts (85%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/types.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/media-type.repository.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/sources/media-type.detail.server.data.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.repository.ts rename src/Umbraco.Web.UI.Client/src/packages/media/media-types/{repository/sources/media-type.tree.server.data.ts => tree/media-type.tree.server.data-source.ts} (81%) rename src/Umbraco.Web.UI.Client/src/packages/media/media-types/{repository => tree}/media-type.tree.store.ts (61%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/types.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/index.ts new file mode 100644 index 0000000000..97f2c76252 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/index.ts @@ -0,0 +1,7 @@ +import './components/index.js'; + +export * from './repository/index.js'; + +export const MEDIA_TYPE_ROOT_ENTITY_TYPE = 'media-type-root'; +export const MEDIA_TYPE_ENTITY_TYPE = 'media-type'; +export const MEDIA_TYPE_FOLDER_ENTITY_TYPE = 'media-type-folder'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/index.ts new file mode 100644 index 0000000000..50410ebfa6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/index.ts @@ -0,0 +1,3 @@ +export { UmbMediaTypeDetailRepository } from './media-type-detail.repository.js'; +export { MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, MEDIA_TYPE_DETAIL_STORE_ALIAS } from './manifests.js'; +export { UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT } from './media-type-detail.store.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/manifests.ts new file mode 100644 index 0000000000..386a16c384 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/manifests.ts @@ -0,0 +1,22 @@ +import { UmbMediaTypeDetailRepository } from './media-type-detail.repository.js'; +import { UmbMediaTypeDetailStore } from './media-type-detail.store.js'; +import { ManifestRepository, ManifestStore } from '@umbraco-cms/backoffice/extension-registry'; + +export const MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS = 'Umb.Repository.MediaType.Detail'; +export const MEDIA_TYPE_DETAIL_STORE_ALIAS = 'Umb.Store.MediaType.Detail'; + +const detailRepository: ManifestRepository = { + type: 'repository', + alias: MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, + name: 'Media Types Repository', + api: UmbMediaTypeDetailRepository, +}; + +const detailStore: ManifestStore = { + type: 'store', + alias: MEDIA_TYPE_DETAIL_STORE_ALIAS, + name: 'Media Type Store', + api: UmbMediaTypeDetailStore, +}; + +export const manifests = [detailRepository, detailStore]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts new file mode 100644 index 0000000000..11f09fa0ed --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts @@ -0,0 +1,179 @@ +import { UMB_MEDIA_TYPE_TREE_STORE_CONTEXT, UmbMediaTypeTreeStore } from '../../tree/media-type.tree.store.js'; +import { UMB_MEDIA_TYPE_ITEM_STORE_CONTEXT, UmbMediaTypeItemStore } from '../item/media-type-item.store.js'; +import { UmbMediaTypeServerDataSource } from './media-type.server.data.js'; +import { UmbMediaTypeDetailStore, UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT } from './media-type-detail.store.js'; +import { type UmbDetailRepository } from '@umbraco-cms/backoffice/repository'; +import { UmbBaseController, type UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { + CreateMediaTypeRequestModel, + MediaTypeResponseModel, + FolderTreeItemResponseModel, + UpdateMediaTypeRequestModel, +} from '@umbraco-cms/backoffice/backend-api'; +import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification'; +import { UmbApi } from '@umbraco-cms/backoffice/extension-api'; + +type ItemType = MediaTypeResponseModel; + +export class UmbMediaTypeDetailRepository + extends UmbBaseController + implements + UmbDetailRepository, + UmbApi +{ + #init!: Promise; + + #treeStore?: UmbMediaTypeTreeStore; + + #detailDataSource: UmbMediaTypeServerDataSource; + #detailStore?: UmbMediaTypeDetailStore; + + #itemStore?: UmbMediaTypeItemStore; + + #notificationContext?: UmbNotificationContext; + + constructor(host: UmbControllerHostElement) { + super(host); + + // TODO: figure out how spin up get the correct data source + this.#detailDataSource = new UmbMediaTypeServerDataSource(this); + + this.#init = Promise.all([ + this.consumeContext(UMB_MEDIA_TYPE_TREE_STORE_CONTEXT, (instance) => { + this.#treeStore = instance; + }), + + this.consumeContext(UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT, (instance) => { + this.#detailStore = instance; + }), + + this.consumeContext(UMB_MEDIA_TYPE_ITEM_STORE_CONTEXT, (instance) => { + this.#itemStore = instance; + }), + + this.consumeContext(UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => { + this.#notificationContext = instance; + }), + ]); + } + + // DETAILS: + + async createScaffold(parentId: string | null) { + if (parentId === undefined) throw new Error('Parent id is missing'); + await this.#init; + + const { data } = await this.#detailDataSource.createScaffold(parentId); + + if (data) { + this.#detailStore?.append(data); + } + + return { data }; + } + + async requestById(id: string) { + if (!id) throw new Error('Id is missing'); + await this.#init; + + const { data, error } = await this.#detailDataSource.get(id); + + if (data) { + this.#detailStore?.append(data); + } + + return { data, error, asObservable: () => this.#detailStore!.byId(id) }; + } + + async byId(id: string) { + if (!id) throw new Error('Id is missing'); + await this.#init; + return this.#detailStore!.byId(id); + } + + // TODO: we need to figure out where to put this + async requestAllowedChildTypesOf(id: string) { + if (!id) throw new Error('Id is missing'); + await this.#init; + return this.#detailDataSource.getAllowedChildrenOf(id); + } + + // Could potentially be general methods: + + async create(mediaType: ItemType) { + if (!mediaType || !mediaType.id) throw new Error('Media Type is missing'); + await this.#init; + + const { error } = await this.#detailDataSource.insert(mediaType); + + if (!error) { + this.#detailStore?.append(mediaType); + const treeItem = createTreeItem(mediaType); + this.#treeStore?.appendItems([treeItem]); + } + + return { error }; + } + + async save(id: string, item: UpdateMediaTypeRequestModel) { + if (!id) throw new Error('Id is missing'); + if (!item) throw new Error('Item is missing'); + + await this.#init; + + const { error } = await this.#detailDataSource.update(id, item); + + if (!error) { + // TODO: we currently don't use the detail store for anything. + // Consider to look up the data before fetching from the server + // Consider notify a workspace if a template is updated in the store while someone is editing it. + this.#detailStore?.append(item); + this.#treeStore?.updateItem(id, item); + // TODO: would be nice to align the stores on methods/methodNames. + + const notification = { data: { message: `Media Type saved` } }; + this.#notificationContext?.peek('positive', notification); + } + + return { error }; + } + + // General: + async delete(id: string) { + if (!id) throw new Error('Media Type id is missing'); + await this.#init; + + const { error } = await this.#detailDataSource.delete(id); + + if (!error) { + const notification = { data: { message: `Media Type deleted` } }; + this.#notificationContext?.peek('positive', notification); + + // TODO: we currently don't use the detail store for anything. + // Consider to look up the data before fetching from the server. + // Consider notify a workspace if a template is deleted from the store while someone is editing it. + // TODO: would be nice to align the stores on methods/methodNames. + this.#detailStore?.removeItem(id); + this.#treeStore?.removeItem(id); + this.#itemStore?.removeItem(id); + } + + return { error }; + } +} + +export const createTreeItem = (item: ItemType): FolderTreeItemResponseModel => { + if (!item) throw new Error('item is null or undefined'); + if (!item.id) throw new Error('item.id is null or undefined'); + + // TODO: needs parentID, this is missing in the current model. Should be good when updated to a createModel. + return { + type: 'media-type', + parentId: null, + name: item.name, + id: item.id, + isFolder: false, + isContainer: false, + hasChildren: false, + }; +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/media-type.detail.store.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.store.ts similarity index 53% rename from src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/media-type.detail.store.ts rename to src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.store.ts index 75b924d9b9..dfa6a13e3e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/media-type.detail.store.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.store.ts @@ -1,35 +1,39 @@ +import { MediaTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; import { UmbStoreBase } from '@umbraco-cms/backoffice/store'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; -import { MediaTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; /** * @export - * @class UmbMediaTypeDetailStore + * @class UmbMediaTypeStore * @extends {UmbStoreBase} - * @description - Details Data Store for Media Types + * @description - Data Store for Media Types */ -export class UmbMediaTypeStore extends UmbStoreBase { +export class UmbMediaTypeDetailStore extends UmbStoreBase { + /** + * Creates an instance of UmbMediaTypeStore. + * @param {UmbControllerHostElement} host + * @memberof UmbMediaTypeStore + */ constructor(host: UmbControllerHostElement) { super( host, - UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN.toString(), + UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT.toString(), new UmbArrayState([], (x) => x.id), ); } + /** + * @param {MediaTypeResponseModel['id']} id + * @return {*} + * @memberof UmbMediaTypeDetailStore + */ byId(id: MediaTypeResponseModel['id']) { return this._data.asObservablePart((x) => x.find((y) => y.id === id)); } - - append(mediaType: MediaTypeResponseModel) { - this._data.append([mediaType]); - } - - remove(uniques: string[]) { - this._data.remove(uniques); - } } -export const UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbMediaTypeStore'); +export const UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT = new UmbContextToken( + 'UmbMediaTypeDetailStore', +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type.server.data.ts new file mode 100644 index 0000000000..c54ad99532 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type.server.data.ts @@ -0,0 +1,147 @@ +import type { UmbDataSource } from '@umbraco-cms/backoffice/repository'; +import { + CreateMediaTypeRequestModel, + MediaTypeResource, + MediaTypeResponseModel, + UpdateMediaTypeRequestModel, +} from '@umbraco-cms/backoffice/backend-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; +import { UmbId } from '@umbraco-cms/backoffice/id'; + +/** + * A data source for the Media Type that fetches data from the server + * @export + * @class UmbMediaTypeServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbMediaTypeServerDataSource + implements UmbDataSource +{ + #host: UmbControllerHost; + + /** + * Creates an instance of UmbMediaServerDataSource. + * @param {UmbControllerHost} host + * @memberof UmbMediaServerDataSource + */ + constructor(host: UmbControllerHost) { + this.#host = host; + } + + /** + * Fetches a Media with the given id from the server + * @param {string} id + * @return {*} + * @memberof UmbMediaTypeServerDataSource + */ + async get(id: string) { + if (!id) { + throw new Error('Id is missing'); + } + + return tryExecuteAndNotify( + this.#host, + MediaTypeResource.getMediaTypeById({ + id: id, + }), + ); + } + + /** + * Creates a new Media scaffold + * @param {(string | null)} parentId + * @return {*} + * @memberof UmbMediaTypeServerDataSource + */ + async createScaffold(parentId: string | null) { + //, parentId: string | null + const data: MediaTypeResponseModel = { + id: UmbId.new(), + //parentId: parentId, + name: '', + alias: '', + description: '', + icon: 'icon-media', + allowedAsRoot: false, + variesByCulture: false, + variesBySegment: false, + isElement: false, + allowedContentTypes: [], + compositions: [], + properties: [], + containers: [], + }; + + return { data }; + } + + /** + * Inserts a new Media Type on the server + * @param {CreateMediaTypeRequestModel} mediaType + * @return {*} + * @memberof UmbMediaTypeServerDataSource + */ + async insert(mediaType: CreateMediaTypeRequestModel) { + if (!mediaType) throw new Error('Media Type is missing'); + return tryExecuteAndNotify( + this.#host, + MediaTypeResource.postMediaType({ + requestBody: mediaType, + }), + ); + } + + /** + * Updates a Media Type on the server + * @param {string} id + * @param {Media} mediaType + * @return {*} + * @memberof UmbMediaTypeServerDataSource + */ + async update(id: string, mediaType: UpdateMediaTypeRequestModel) { + if (!id) throw new Error('Id is missing'); + + mediaType = { ...mediaType }; + + // TODO: Hack to remove some props that ruins the media-type post end-point. + (mediaType as any).id = undefined; + + return tryExecuteAndNotify(this.#host, MediaTypeResource.putMediaTypeById({ id, requestBody: mediaType })); + } + + /** + * Deletes a Template on the server + * @param {string} id + * @return {*} + * @memberof UmbMediaTypeServerDataSource + */ + async delete(id: string) { + if (!id) { + throw new Error('Id is missing'); + } + + // TODO: Hack the type to avoid type-error here: + return tryExecuteAndNotify(this.#host, MediaTypeResource.deleteMediaTypeById({ id })) as any; + } + + /** + * Get the allowed media types for a given parent id + * @param {string} id + * @return {*} + * @memberof UmbMediaTypeServerDataSource + */ + async getAllowedChildrenOf(id: string) { + if (!id) throw new Error('Id is missing'); + + return tryExecuteAndNotify( + this.#host, + fetch(`/umbraco/management/api/v1/media-type/allowed-children-of/${id}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }).then((res) => res.json()), + ); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/index.ts new file mode 100644 index 0000000000..1f37e6d2ac --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/index.ts @@ -0,0 +1,2 @@ +export * from './item/index.js'; +export * from './detail/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/index.ts new file mode 100644 index 0000000000..eb23683fec --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/index.ts @@ -0,0 +1,3 @@ +export { UmbMediaTypeItemRepository } from './media-type-item.repository.js'; +export { MEDIA_TYPE_ITEM_REPOSITORY_ALIAS, MEDIA_TYPE_ITEM_STORE_ALIAS } from './manifests.js'; +export * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/manifests.ts new file mode 100644 index 0000000000..0793bcbf34 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/manifests.ts @@ -0,0 +1,22 @@ +import { UmbMediaTypeItemRepository } from './media-type-item.repository.js'; +import { UmbMediaTypeItemStore } from './media-type-item.store.js'; +import { ManifestItemStore, ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; + +export const MEDIA_TYPE_ITEM_REPOSITORY_ALIAS = 'Umb.Repository.MediaType.Item'; +export const MEDIA_TYPE_ITEM_STORE_ALIAS = 'Umb.Store.MediaType.Item'; + +const itemRepository: ManifestRepository = { + type: 'repository', + alias: MEDIA_TYPE_ITEM_REPOSITORY_ALIAS, + name: 'Media Type Item Repository', + api: UmbMediaTypeItemRepository, +}; + +const itemStore: ManifestItemStore = { + type: 'itemStore', + alias: MEDIA_TYPE_ITEM_STORE_ALIAS, + name: 'Media Type Item Store', + api: UmbMediaTypeItemStore, +}; + +export const manifests = [itemRepository, itemStore]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.repository.ts new file mode 100644 index 0000000000..06fe6fc814 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.repository.ts @@ -0,0 +1,11 @@ +import { UmbMediaTypeItemModel } from './types.js'; +import { UmbMediaTypeItemServerDataSource } from './media-type-item.server.data.js'; +import { UMB_MEDIA_TYPE_ITEM_STORE_CONTEXT } from './media-type-item.store.js'; +import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbItemRepositoryBase } from '@umbraco-cms/backoffice/repository'; + +export class UmbMediaTypeItemRepository extends UmbItemRepositoryBase { + constructor(host: UmbControllerHost) { + super(host, UmbMediaTypeItemServerDataSource, UMB_MEDIA_TYPE_ITEM_STORE_CONTEXT); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/sources/media-type-item.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.server.data.ts similarity index 96% rename from src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/sources/media-type-item.server.data.ts rename to src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.server.data.ts index 75a3a48ccd..0f466a14ab 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/sources/media-type-item.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.server.data.ts @@ -7,7 +7,7 @@ import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; * A data source for Media Type items that fetches data from the server * @export * @class UmbMediaTypeItemServerDataSource - * @implements {DocumentTreeDataSource} + * @implements {UmbItemDataSource} */ export class UmbMediaTypeItemServerDataSource implements UmbItemDataSource { #host: UmbControllerHost; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/media-type-item.store.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.store.ts similarity index 85% rename from src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/media-type-item.store.ts rename to src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.store.ts index d0c24782bf..0149fb57f8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/media-type-item.store.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.store.ts @@ -23,7 +23,7 @@ export class UmbMediaTypeItemStore constructor(host: UmbControllerHostElement) { super( host, - UMB_MEDIA_TYPE_ITEM_STORE_CONTEXT_TOKEN.toString(), + UMB_MEDIA_TYPE_ITEM_STORE_CONTEXT.toString(), new UmbArrayState([], (x) => x.id), ); } @@ -33,6 +33,4 @@ export class UmbMediaTypeItemStore } } -export const UMB_MEDIA_TYPE_ITEM_STORE_CONTEXT_TOKEN = new UmbContextToken( - 'UmbMediaTypeItemStore', -); +export const UMB_MEDIA_TYPE_ITEM_STORE_CONTEXT = new UmbContextToken('UmbMediaTypeItemStore'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/types.ts new file mode 100644 index 0000000000..30a844af5a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/types.ts @@ -0,0 +1,3 @@ +import { MediaTypeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; + +export type UmbMediaTypeItemModel = MediaTypeItemResponseModel; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/manifests.ts index 2c77e18121..bb35952020 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/manifests.ts @@ -1,32 +1,4 @@ -import { UmbMediaTypeRepository } from './media-type.repository.js'; -import { UmbMediaTypeStore } from './media-type.detail.store.js'; -import { UmbMediaTypeTreeStore } from './media-type.tree.store.js'; -import type { ManifestStore, ManifestTreeStore, ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; +import { manifests as detailManifests } from './detail/manifests.js'; +import { manifests as itemManifests } from './item/manifests.js'; -export const MEDIA_TYPE_REPOSITORY_ALIAS = 'Umb.Repository.MediaType'; - -const repository: ManifestRepository = { - type: 'repository', - alias: MEDIA_TYPE_REPOSITORY_ALIAS, - name: 'Media Type Repository', - api: UmbMediaTypeRepository, -}; - -export const MEDIA_TYPE_STORE_ALIAS = 'Umb.Store.MediaType'; -export const MEDIA_TYPE_TREE_STORE_ALIAS = 'Umb.Store.MediaTypeTree'; - -const store: ManifestStore = { - type: 'store', - alias: MEDIA_TYPE_STORE_ALIAS, - name: 'Media Type Store', - api: UmbMediaTypeStore, -}; - -const treeStore: ManifestTreeStore = { - type: 'treeStore', - alias: MEDIA_TYPE_TREE_STORE_ALIAS, - name: 'Media Type Tree Store', - api: UmbMediaTypeTreeStore, -}; - -export const manifests = [store, treeStore, repository]; +export const manifests = [...detailManifests, ...itemManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/media-type.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/media-type.repository.ts deleted file mode 100644 index dea0b3010e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/media-type.repository.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { UmbMediaTypeTreeStore, UMB_MEDIA_TYPE_TREE_STORE_CONTEXT_TOKEN } from './media-type.tree.store.js'; -import { UmbMediaTypeDetailServerDataSource } from './sources/media-type.detail.server.data.js'; -import { UmbMediaTypeStore, UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN } from './media-type.detail.store.js'; -import { UmbMediaTypeTreeServerDataSource } from './sources/media-type.tree.server.data.js'; -import { UMB_MEDIA_TYPE_ITEM_STORE_CONTEXT_TOKEN, UmbMediaTypeItemStore } from './media-type-item.store.js'; -import { UmbMediaTypeItemServerDataSource } from './sources/media-type-item.server.data.js'; -import { UmbBaseController, UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification'; -import { - UmbDataSource, - UmbItemRepository, - UmbDetailRepository, - UmbItemDataSource, -} from '@umbraco-cms/backoffice/repository'; -import { UmbTreeRepository, UmbTreeDataSource } from '@umbraco-cms/backoffice/tree'; -import { - CreateMediaTypeRequestModel, - FolderTreeItemResponseModel, - MediaTypeItemResponseModel, - MediaTypeResponseModel, - UpdateMediaTypeRequestModel, -} from '@umbraco-cms/backoffice/backend-api'; -import { UmbApi } from '@umbraco-cms/backoffice/extension-api'; - -export class UmbMediaTypeRepository - extends UmbBaseController - implements - UmbItemRepository, - UmbTreeRepository, - UmbDetailRepository, - UmbApi -{ - #init!: Promise; - - #treeSource: UmbTreeDataSource; - #treeStore?: UmbMediaTypeTreeStore; - - #detailSource: UmbDataSource; - #detailStore?: UmbMediaTypeStore; - - #itemSource: UmbItemDataSource; - #itemStore?: UmbMediaTypeItemStore; - - #notificationContext?: UmbNotificationContext; - - constructor(host: UmbControllerHostElement) { - super(host); - - // TODO: figure out how spin up get the correct data source - this.#treeSource = new UmbMediaTypeTreeServerDataSource(this); - this.#detailSource = new UmbMediaTypeDetailServerDataSource(this); - this.#itemSource = new UmbMediaTypeItemServerDataSource(this); - - this.#init = Promise.all([ - this.consumeContext(UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN, (instance) => { - this.#detailStore = instance; - }), - - this.consumeContext(UMB_MEDIA_TYPE_TREE_STORE_CONTEXT_TOKEN, (instance) => { - this.#treeStore = instance; - }), - - this.consumeContext(UMB_MEDIA_TYPE_ITEM_STORE_CONTEXT_TOKEN, (instance) => { - this.#itemStore = instance; - }), - - this.consumeContext(UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => { - this.#notificationContext = instance; - }), - ]); - } - - async requestTreeRoot() { - await this.#init; - - const data = { - id: null, - type: 'media-type-root', - name: 'Media Types', - icon: 'icon-folder', - hasChildren: true, - }; - - return { data }; - } - - async requestRootTreeItems() { - await this.#init; - - const { data, error } = await this.#treeSource.getRootItems(); - - if (data) { - this.#treeStore?.appendItems(data.items); - } - - return { data, error, asObservable: () => this.#treeStore!.rootItems }; - } - - async requestTreeItemsOf(parentId: string | null) { - await this.#init; - if (parentId === undefined) throw new Error('Parent id is missing'); - - const { data, error } = await this.#treeSource.getChildrenOf(parentId); - - if (data) { - this.#treeStore?.appendItems(data.items); - } - - return { data, error, asObservable: () => this.#treeStore!.childrenOf(parentId) }; - } - - async byId(id: string) { - if (!id) throw new Error('Key is missing'); - await this.#init; - return this.#detailStore!.byId(id); - } - - async requestItemsLegacy(ids: Array) { - await this.#init; - - if (!ids) { - throw new Error('Ids are missing'); - } - - const { data, error } = await this.#treeSource.getItems(ids); - - return { data, error, asObservable: () => this.#treeStore!.items(ids) }; - } - - async rootTreeItems() { - await this.#init; - return this.#treeStore!.rootItems; - } - - async treeItemsOf(parentId: string | null) { - await this.#init; - return this.#treeStore!.childrenOf(parentId); - } - - async itemsLegacy(ids: Array) { - await this.#init; - return this.#treeStore!.items(ids); - } - - // DETAILS - - async createScaffold(parentId: string | null) { - if (parentId === undefined) throw new Error('Parent id is missing'); - await this.#init; - return this.#detailSource.createScaffold(parentId); - } - - async requestById(id: string) { - await this.#init; - if (!id) { - throw new Error('Id is missing'); - } - const { data, error } = await this.#detailSource.get(id); - - if (data) { - this.#detailStore?.append(data); - } - return { data, error }; - } - - async requestItems(ids: Array) { - if (!ids) throw new Error('Keys are missing'); - await this.#init; - - const { data, error } = await this.#itemSource.getItems(ids); - - if (data) { - this.#itemStore?.appendItems(data); - } - - return { data, error, asObservable: () => this.#itemStore!.items(ids) }; - } - - async items(ids: Array) { - await this.#init; - return this.#itemStore!.items(ids); - } - - async delete(id: string) { - await this.#init; - return this.#detailSource.delete(id); - } - - async save(id: string, item: UpdateMediaTypeRequestModel) { - if (!id) throw new Error('Data Type id is missing'); - if (!item) throw new Error('Media Type is missing'); - await this.#init; - - const { error } = await this.#detailSource.update(id, item); - - if (!error) { - this.#detailStore?.append(item); - this.#treeStore?.updateItem(id, item); - - const notification = { data: { message: `Media type '${item.name}' saved` } }; - this.#notificationContext?.peek('positive', notification); - } - - return { error }; - } - - async create(mediaType: CreateMediaTypeRequestModel) { - if (!mediaType || !mediaType.id) throw new Error('Document Type is missing'); - await this.#init; - - const { error } = await this.#detailSource.insert(mediaType); - - if (!error) { - //TODO: Model mismatch. FIX - this.#detailStore?.append(mediaType as unknown as MediaTypeResponseModel); - - const treeItem = { - type: 'media-type', - parentId: null, - name: mediaType.name, - id: mediaType.id, - isFolder: false, - isContainer: false, - hasChildren: false, - }; - this.#treeStore?.appendItems([treeItem]); - } - - return { error }; - } - - async move() { - alert('move me!'); - } - - async copy() { - alert('copy me'); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/sources/media-type.detail.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/sources/media-type.detail.server.data.ts deleted file mode 100644 index def4193628..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/sources/media-type.detail.server.data.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { - CreateMediaTypeRequestModel, - MediaTypeResource, - MediaTypeResponseModel, - UpdateMediaTypeRequestModel, -} from '@umbraco-cms/backoffice/backend-api'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; -import { UmbDataSource } from '@umbraco-cms/backoffice/repository'; - -/** - * @description - A data source for the Media Type detail that fetches data from the server - * @export - * @class UmbMediaTypeDetailServerDataSource - * @implements {MediaTypeDetailDataSource} - */ -export class UmbMediaTypeDetailServerDataSource - implements UmbDataSource -{ - #host: UmbControllerHost; - - constructor(host: UmbControllerHost) { - this.#host = host; - } - - /** - * @description - Creates a new MediaType scaffold - * @return {*} - * @memberof UmbMediaTypeDetailServerDataSource - */ - async createScaffold() { - const data: CreateMediaTypeRequestModel = { - name: '', - } as CreateMediaTypeRequestModel; - - return { data }; - } - - /** - * @description - Fetches a MediaType with the given id from the server - * @param {string} id - * @return {*} - * @memberof UmbMediaTypeDetailServerDataSource - */ - async get(id: string) { - if (!id) throw new Error('Key is missing'); - return tryExecuteAndNotify( - this.#host, - MediaTypeResource.getMediaTypeById({ - id: id, - }), - ); - } - - /** - * @description - Updates a MediaType on the server - * @param {UpdateMediaTypeRequestModel} MediaType - * @return {*} - * @memberof UmbMediaTypeDetailServerDataSource - */ - async update(id: string, data: UpdateMediaTypeRequestModel) { - if (!id) throw new Error('Key is missing'); - - return tryExecuteAndNotify( - this.#host, - MediaTypeResource.putMediaTypeById({ - id: id, - requestBody: data, - }), - ); - } - - /** - * @description - Inserts a new MediaType on the server - * @param {CreateMediaTypeRequestModel} data - * @return {*} - * @memberof UmbMediaTypeDetailServerDataSource - */ - async insert(mediaType: CreateMediaTypeRequestModel) { - if (!mediaType) throw new Error('Media type is missing'); - if (!mediaType.id) throw new Error('Media type id is missing'); - - return tryExecuteAndNotify( - this.#host, - MediaTypeResource.postMediaType({ - requestBody: mediaType, - }), - ); - } - - /** - * @description - Deletes a MediaType on the server - * @param {string} id - * @return {*} - * @memberof UmbMediaTypeDetailServerDataSource - */ - async delete(id: string) { - if (!id) throw new Error('Key is missing'); - - return tryExecuteAndNotify( - this.#host, - MediaTypeResource.deleteMediaTypeById({ - id: id, - }), - ); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/manifests.ts index 5d1ec30811..bbde15f412 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/manifests.ts @@ -1,12 +1,36 @@ -import { MEDIA_TYPE_REPOSITORY_ALIAS } from '../repository/manifests.js'; -import type { ManifestTree, ManifestTreeItem } from '@umbraco-cms/backoffice/extension-registry'; +import { UmbMediaTypeTreeRepository } from './media-type-tree.repository.js'; +import { UmbMediaTypeTreeStore } from './media-type.tree.store.js'; +import type { + ManifestRepository, + ManifestTree, + ManifestTreeItem, + ManifestTreeStore, +} from '@umbraco-cms/backoffice/extension-registry'; + +export const MEDIA_TYPE_TREE_REPOSITORY_ALIAS = 'Umb.Repository.MediaType.Tree'; +export const MEDIA_TYPE_TREE_STORE_ALIAS = 'Umb.Store.MediaType.Tree'; +export const MEDIA_TYPE_TREE_ALIAS = 'Umb.Tree.MediaType'; + +const treeRepository: ManifestRepository = { + type: 'repository', + alias: MEDIA_TYPE_TREE_REPOSITORY_ALIAS, + name: 'Media Type Tree Repository', + api: UmbMediaTypeTreeRepository, +}; + +const treeStore: ManifestTreeStore = { + type: 'treeStore', + alias: MEDIA_TYPE_TREE_STORE_ALIAS, + name: 'Media Type Tree Store', + api: UmbMediaTypeTreeStore, +}; const tree: ManifestTree = { type: 'tree', - alias: 'Umb.Tree.MediaTypes', - name: 'Media Types Tree', + alias: MEDIA_TYPE_TREE_ALIAS, + name: 'Media Type Tree', meta: { - repositoryAlias: MEDIA_TYPE_REPOSITORY_ALIAS, + repositoryAlias: MEDIA_TYPE_TREE_REPOSITORY_ALIAS, }, }; @@ -20,4 +44,4 @@ const treeItem: ManifestTreeItem = { }, }; -export const manifests = [tree, treeItem]; +export const manifests = [treeRepository, treeStore, tree, treeItem]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.repository.ts new file mode 100644 index 0000000000..8096edbcf6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.repository.ts @@ -0,0 +1,28 @@ +import { MEDIA_TYPE_ROOT_ENTITY_TYPE } from '../index.js'; +import { UmbMediaTypeTreeServerDataSource } from './media-type.tree.server.data-source.js'; +import { UMB_MEDIA_TYPE_TREE_STORE_CONTEXT } from './media-type.tree.store.js'; +import { UmbMediaTypeTreeItemModel, UmbMediaTypeTreeRootModel } from './types.js'; +import { UmbTreeRepositoryBase } from '@umbraco-cms/backoffice/tree'; +import { type UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbApi } from '@umbraco-cms/backoffice/extension-api'; + +export class UmbMediaTypeTreeRepository + extends UmbTreeRepositoryBase + implements UmbApi +{ + constructor(host: UmbControllerHost) { + super(host, UmbMediaTypeTreeServerDataSource, UMB_MEDIA_TYPE_TREE_STORE_CONTEXT); + } + + async requestTreeRoot() { + const data = { + id: null, + type: MEDIA_TYPE_ROOT_ENTITY_TYPE, + name: 'Media Types', + icon: 'icon-folder', + hasChildren: true, + }; + + return { data }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/sources/media-type.tree.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type.tree.server.data-source.ts similarity index 81% rename from src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/sources/media-type.tree.server.data.ts rename to src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type.tree.server.data-source.ts index db42fb34d2..278aa06399 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/sources/media-type.tree.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type.tree.server.data-source.ts @@ -1,10 +1,10 @@ +import type { UmbTreeDataSource } from '@umbraco-cms/backoffice/tree'; import { MediaTypeResource } from '@umbraco-cms/backoffice/backend-api'; -import { type UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { type UmbTreeDataSource } from '@umbraco-cms/backoffice/tree'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; /** - * A data source for the MediaType tree that fetches data from the server + * A data source for the Media Type tree that fetches data from the server * @export * @class UmbMediaTypeTreeServerDataSource * @implements {UmbTreeDataSource} @@ -13,9 +13,9 @@ export class UmbMediaTypeTreeServerDataSource implements UmbTreeDataSource { #host: UmbControllerHost; /** - * Creates an instance of MediaTypeTreeDataSource. + * Creates an instance of UmbMediaTypeTreeServerDataSource. * @param {UmbControllerHost} host - * @memberof MediaTypeTreeDataSource + * @memberof UmbMediaTypeTreeServerDataSource */ constructor(host: UmbControllerHost) { this.#host = host; @@ -60,8 +60,8 @@ export class UmbMediaTypeTreeServerDataSource implements UmbTreeDataSource { * @memberof UmbMediaTypeTreeServerDataSource */ async getItems(ids: Array) { - if (!ids || ids.length === 0) { - throw new Error('Keys are missing'); + if (ids) { + throw new Error('Ids are missing'); } return tryExecuteAndNotify( diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/media-type.tree.store.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type.tree.store.ts similarity index 61% rename from src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/media-type.tree.store.ts rename to src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type.tree.store.ts index b5b1bbf4e5..d00506352d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/media-type.tree.store.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type.tree.store.ts @@ -1,11 +1,11 @@ import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbEntityTreeStore } from '@umbraco-cms/backoffice/tree'; -import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; /** * @export * @class UmbMediaTypeTreeStore - * @extends {UmbEntityTreeStore} + * @extends {UmbStoreBase} * @description - Tree Data Store for Media Types */ export class UmbMediaTypeTreeStore extends UmbEntityTreeStore { @@ -15,10 +15,8 @@ export class UmbMediaTypeTreeStore extends UmbEntityTreeStore { * @memberof UmbMediaTypeTreeStore */ constructor(host: UmbControllerHostElement) { - super(host, UMB_MEDIA_TYPE_TREE_STORE_CONTEXT_TOKEN.toString()); + super(host, UMB_MEDIA_TYPE_TREE_STORE_CONTEXT.toString()); } } -export const UMB_MEDIA_TYPE_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken( - 'UmbMediaTypeTreeStore', -); +export const UMB_MEDIA_TYPE_TREE_STORE_CONTEXT = new UmbContextToken('UmbMediaTypeTreeStore'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/types.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/types.ts new file mode 100644 index 0000000000..b894e63940 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/types.ts @@ -0,0 +1,5 @@ +import type { MediaTypeTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import type { UmbEntityTreeItemModel, UmbEntityTreeRootModel } from '@umbraco-cms/backoffice/tree'; + +export type UmbMediaTypeTreeItemModel = MediaTypeTreeItemResponseModel & UmbEntityTreeItemModel; +export type UmbMediaTypeTreeRootModel = MediaTypeTreeItemResponseModel & UmbEntityTreeRootModel; From 5c20b8c1ee570c746212e72891345d12f1e8234e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Thu, 16 Nov 2023 18:40:36 +1300 Subject: [PATCH 03/25] MEGA rework --- .../src/packages/core/modal/token/index.ts | 1 + .../token/media-type-picker-modal.token.ts | 16 + .../media/media-types/components/index.ts | 1 + .../media-type-input.context.ts | 11 + .../media-type-input.element.ts | 146 +++++ .../media-types/entity-actions/manifests.ts | 4 +- .../packages/media/media-types/manifests.ts | 8 +- .../media/media-types/menu-item/manifests.ts | 3 +- .../media/media-types/workspace/manifests.ts | 46 +- .../workspace/media-type-workspace.context.ts | 116 ++-- ...-workspace-view-edit-properties.element.ts | 214 ++++++++ ...pe-workspace-view-edit-property.element.ts | 478 ++++++++++++++++ ...ia-type-workspace-view-edit-tab.element.ts | 272 +++++++++ .../media-type-workspace-view-edit.element.ts | 518 ++++++++++++++++++ ...edia-type-design-workspace-view.element.ts | 68 --- ...a-type-list-view-workspace-view.element.ts | 68 --- ...type-permissions-workspace-view.element.ts | 68 --- ...a-type-workspace-view-structure.element.ts | 117 ++++ 18 files changed, 1873 insertions(+), 282 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/modal/token/media-type-picker-modal.token.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/media-type-input/media-type-input.context.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/media-type-input/media-type-input.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-properties.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-property.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-tab.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/details/media-type-design-workspace-view.element.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/details/media-type-list-view-workspace-view.element.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/details/media-type-permissions-workspace-view.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/structure/media-type-workspace-view-structure.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/index.ts index 666c6a40c4..0299bedaaf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/index.ts @@ -19,6 +19,7 @@ export * from './invite-user-modal.token.js'; export * from './language-picker-modal.token.js'; export * from './link-picker-modal.token.js'; export * from './media-tree-picker-modal.token.js'; +export * from './media-type-picker-modal.token.js'; export * from './property-editor-ui-picker-modal.token.js'; export * from './property-settings-modal.token.js'; export * from './search-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/media-type-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/media-type-picker-modal.token.ts new file mode 100644 index 0000000000..aef85910f2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/media-type-picker-modal.token.ts @@ -0,0 +1,16 @@ +import { EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { UmbModalToken, UmbPickerModalValue, UmbTreePickerModalData } from '@umbraco-cms/backoffice/modal'; + +export type UmbMediaTypePickerModalData = UmbTreePickerModalData; +export type UmbMediaTypePickerModalValue = UmbPickerModalValue; + +export const UMB_MEDIA_TYPE_PICKER_MODAL = new UmbModalToken( + 'Umb.Modal.TreePicker', + { + type: 'sidebar', + size: 'small', + }, + { + treeAlias: 'Umb.Tree.MediaType', + }, +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/index.ts new file mode 100644 index 0000000000..6e336698ee --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/index.ts @@ -0,0 +1 @@ +import './media-type-input/media-type-input.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/media-type-input/media-type-input.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/media-type-input/media-type-input.context.ts new file mode 100644 index 0000000000..2642d93198 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/media-type-input/media-type-input.context.ts @@ -0,0 +1,11 @@ +import { MEDIA_TYPE_ITEM_REPOSITORY_ALIAS } from '../../repository/index.js'; +import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UMB_MEDIA_TYPE_PICKER_MODAL } from '@umbraco-cms/backoffice/modal'; +import { MediaTypeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; + +export class UmbMediaTypePickerContext extends UmbPickerInputContext { + constructor(host: UmbControllerHostElement) { + super(host, MEDIA_TYPE_ITEM_REPOSITORY_ALIAS, UMB_MEDIA_TYPE_PICKER_MODAL); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/media-type-input/media-type-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/media-type-input/media-type-input.element.ts new file mode 100644 index 0000000000..1a3da5ed39 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/media-type-input/media-type-input.element.ts @@ -0,0 +1,146 @@ +import { UmbMediaTypePickerContext } from './media-type-input.context.js'; +import { css, html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import type { MediaTypeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; + +@customElement('umb-media-type-input') +export class UmbMediaTypeInputElement extends FormControlMixin(UmbLitElement) { + /** + * This is a minimum amount of selected items in this input. + * @type {number} + * @attr + * @default 0 + */ + @property({ type: Number }) + public get min(): number { + return this.#pickerContext.min; + } + public set min(value: number) { + this.#pickerContext.min = value; + } + + /** + * Min validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'min-message' }) + minMessage = 'This field need more items'; + + /** + * This is a maximum amount of selected items in this input. + * @type {number} + * @attr + * @default Infinity + */ + @property({ type: Number }) + public get max(): number { + return this.#pickerContext.max; + } + public set max(value: number) { + this.#pickerContext.max = value; + } + + /** + * Max validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'min-message' }) + maxMessage = 'This field exceeds the allowed amount of items'; + + public get selectedIds(): Array { + return this.#pickerContext.getSelection(); + } + public set selectedIds(ids: Array) { + this.#pickerContext.setSelection(ids); + } + + @property() + public set value(idsString: string) { + // Its with full purpose we don't call super.value, as thats being handled by the observation of the context selection. + this.selectedIds = idsString.split(/[ ,]+/); + } + + @property() + get pickableFilter() { + return this.#pickerContext.pickableFilter; + } + set pickableFilter(newVal) { + this.#pickerContext.pickableFilter = newVal; + } + + @state() + private _items?: Array; + + #pickerContext = new UmbMediaTypePickerContext(this); + + constructor() { + super(); + + this.addValidator( + 'rangeUnderflow', + () => this.minMessage, + () => !!this.min && this.#pickerContext.getSelection().length < this.min, + ); + + this.addValidator( + 'rangeOverflow', + () => this.maxMessage, + () => !!this.max && this.#pickerContext.getSelection().length > this.max, + ); + + this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(','))); + this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); + } + + protected getFormElement() { + return undefined; + } + + render() { + console.log('ITEMS', this._items); + return html` + ${this._items?.map((item) => this._renderItem(item))} + this.#pickerContext.openPicker()} label="open" + >Add + `; + } + + private _renderItem(item: MediaTypeItemResponseModel) { + if (!item.id) return; + + //TODO: Using uui-ref-node as we don't have a uui-ref-media-type yet. + return html` + + + this.#pickerContext.requestRemoveItem(item.id!)} + label="Remove Media Type ${item.name}" + >Remove + + + `; + } + + static styles = [ + css` + #add-button { + width: 100%; + } + `, + ]; +} + +export default UmbMediaTypeInputElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-media-type-input': UmbMediaTypeInputElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/manifests.ts index dfe68d7d56..0d68d6334b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/manifests.ts @@ -1,11 +1,11 @@ -import { MEDIA_TYPE_REPOSITORY_ALIAS } from '../repository/manifests.js'; +import { MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS } from '../index.js'; import { UmbCreateMediaTypeEntityAction } from './create.action.js'; import UmbReloadMediaTypeEntityAction from './reload.action.js'; import { UmbDeleteEntityAction, UmbMoveEntityAction, UmbCopyEntityAction } from '@umbraco-cms/backoffice/entity-action'; import type { ManifestEntityAction } from '@umbraco-cms/backoffice/extension-registry'; const entityType = 'media-type'; -const repositoryAlias = MEDIA_TYPE_REPOSITORY_ALIAS; +const repositoryAlias = MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS; const entityActions: Array = [ { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/manifests.ts index ca7cd2be1b..51dc7d38ed 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/manifests.ts @@ -1,13 +1,13 @@ +import { manifests as entityActionsManifests } from './entity-actions/manifests.js'; import { manifests as menuItemManifests } from './menu-item/manifests.js'; +import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; -import { manifests as repositoryManifests } from './repository/manifests.js'; -import { manifests as entityActionManifests } from './entity-actions/manifests.js'; export const manifests = [ + ...entityActionsManifests, ...menuItemManifests, - ...treeManifests, ...repositoryManifests, + ...treeManifests, ...workspaceManifests, - ...entityActionManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu-item/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu-item/manifests.ts index 7b3724966e..0213b79f29 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu-item/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu-item/manifests.ts @@ -1,3 +1,4 @@ +import { MEDIA_TYPE_TREE_ALIAS } from '../tree/manifests.js'; import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; const menuItem: ManifestTypes = { @@ -9,7 +10,7 @@ const menuItem: ManifestTypes = { meta: { label: 'Media Types', icon: 'icon-folder', - treeAlias: 'Umb.Tree.MediaTypes', + treeAlias: MEDIA_TYPE_TREE_ALIAS, menus: ['Umb.Menu.Settings'], }, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/manifests.ts index 99b6c879ff..5456b2290b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/manifests.ts @@ -17,17 +17,17 @@ const workspace: ManifestWorkspace = { }, }; -const workspaceViews: Array = [ +const workspaceEditorViews: Array = [ { type: 'workspaceEditorView', alias: 'Umb.WorkspaceView.MediaType.Design', name: 'Media Type Workspace Design View', - loader: () => import('./views/details/media-type-design-workspace-view.element.js'), - weight: 90, + loader: () => import('./views/design/media-type-workspace-view-edit.element.js'), + weight: 1000, meta: { - label: 'Details', - pathname: 'details', - icon: 'document', + label: 'Design', + pathname: 'design', + icon: 'icon-document-dashed-line', }, conditions: [ { @@ -38,32 +38,14 @@ const workspaceViews: Array = [ }, { type: 'workspaceEditorView', - alias: 'Umb.WorkspaceView.MediaType.ListView', - name: 'Media Type Workspace ListView View', - loader: () => import('./views/details/media-type-list-view-workspace-view.element.js'), - weight: 90, + alias: 'Umb.WorkspaceView.MediaType.Structure', + name: 'Media Type Workspace Structure View', + loader: () => import('./views/structure/media-type-workspace-view-structure.element.js'), + weight: 800, meta: { - label: 'List View', - pathname: 'list-view', - icon: 'document', - }, - conditions: [ - { - alias: 'Umb.Condition.WorkspaceAlias', - match: workspace.alias, - }, - ], - }, - { - type: 'workspaceEditorView', - alias: 'Umb.WorkspaceView.MediaType.Permissions', - name: 'Media Type Workspace Permissions View', - loader: () => import('./views/details/media-type-permissions-workspace-view.element.js'), - weight: 90, - meta: { - label: 'Permissions', - pathname: 'permissions', - icon: 'document', + label: 'Structure', + pathname: 'structure', + icon: 'icon-mindmap', }, conditions: [ { @@ -94,4 +76,4 @@ const workspaceActions: Array = [ }, ]; -export const manifests = [workspace, ...workspaceViews, ...workspaceViewCollections, ...workspaceActions]; +export const manifests = [workspace, ...workspaceEditorViews, ...workspaceViewCollections, ...workspaceActions]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts index 7da542eca1..326f69aa32 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts @@ -1,35 +1,68 @@ -import { UmbMediaTypeRepository } from '../repository/media-type.repository.js'; -import { UmbSaveableWorkspaceContextInterface, UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; +import { UmbMediaTypeDetailRepository } from '../repository/detail/media-type-detail.repository.js'; +import { UmbContentTypePropertyStructureManager } from '@umbraco-cms/backoffice/content-type'; +import { UmbWorkspaceContext, UmbSaveableWorkspaceContextInterface } from '@umbraco-cms/backoffice/workspace'; +import type { + ContentTypeCompositionModel, + ContentTypeSortModel, + MediaTypeResponseModel, +} from '@umbraco-cms/backoffice/backend-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; -import { MediaTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api'; type EntityType = MediaTypeResponseModel; export class UmbMediaTypeWorkspaceContext - extends UmbWorkspaceContext + extends UmbWorkspaceContext implements UmbSaveableWorkspaceContextInterface { - #data = new UmbObjectState(undefined); - data = this.#data.asObservable(); - #getDataPromise?: Promise; + // Draft is located in structure manager - name = this.#data.asObservablePart((data) => data?.name); - id = this.#data.asObservablePart((data) => data?.id); - alias = this.#data.asObservablePart((data) => data?.alias); - description = this.#data.asObservablePart((data) => data?.description); - icon = this.#data.asObservablePart((data) => data?.icon); + // General for content types: + readonly data; + readonly name; + readonly alias; + readonly description; + readonly icon; + + readonly allowedAsRoot; + readonly allowedContentTypes; + readonly compositions; + + readonly structure; + + #isSorting = new UmbBooleanState(undefined); + isSorting = this.#isSorting.asObservable(); constructor(host: UmbControllerHostElement) { - super(host, 'Umb.Workspace.MediaType', new UmbMediaTypeRepository(host)); + super(host, 'Umb.Workspace.MediaType', new UmbMediaTypeDetailRepository(host)); + + this.structure = new UmbContentTypePropertyStructureManager(this.host, this.repository); + + // General for content types: + this.data = this.structure.ownerContentType; + this.name = this.structure.ownerContentTypeObservablePart((data) => data?.name); + this.alias = this.structure.ownerContentTypeObservablePart((data) => data?.alias); + this.description = this.structure.ownerContentTypeObservablePart((data) => data?.description); + this.icon = this.structure.ownerContentTypeObservablePart((data) => data?.icon); + this.allowedAsRoot = this.structure.ownerContentTypeObservablePart((data) => data?.allowedAsRoot); + this.allowedContentTypes = this.structure.ownerContentTypeObservablePart((data) => data?.allowedContentTypes); + this.compositions = this.structure.ownerContentTypeObservablePart((data) => data?.compositions); + } + + getIsSorting() { + return this.#isSorting.getValue(); + } + + setIsSorting(isSorting: boolean) { + this.#isSorting.next(isSorting); } getData() { - return this.#data.getValue(); + return this.structure.getOwnerContentType() || {}; } getEntityId() { - return this.getData()?.id || ''; + return this.getData().id; } getEntityType() { @@ -37,44 +70,49 @@ export class UmbMediaTypeWorkspaceContext } updateProperty(propertyName: PropertyName, value: EntityType[PropertyName]) { - this.#data.update({ [propertyName]: value }); - } - - async load(id: string) { - this.#getDataPromise = this.repository.requestById(id); - const { data } = await this.#getDataPromise; - if (data) { - this.setIsNew(false); - this.#data.update(data); - } + this.structure.updateOwnerContentType({ [propertyName]: value }); } async create(parentId: string | null) { - this.#getDataPromise = this.repository.createScaffold(parentId); - const { data } = await this.#getDataPromise; - if (!data) return; + const { data } = await this.structure.createScaffold(parentId); + if (!data) return undefined; this.setIsNew(true); - - //TODO: Model mismatch. FIX - this.#data.next(data as unknown as MediaTypeResponseModel); + this.setIsSorting(false); + //this.#draft.next(data); + return { data } || undefined; + // TODO: Is this wrong? should we return { data }?? } - async save() { - if (!this.#data.value) return; - if (!this.#data.value.id) return; + async load(entityId: string) { + const { data } = await this.structure.loadType(entityId); + if (!data) return undefined; + this.setIsNew(false); + this.setIsSorting(false); + //this.#draft.next(data); + return { data } || undefined; + // TODO: Is this wrong? should we return { data }?? + } + + /** + * Save or creates the media type, based on wether its a new one or existing. + */ + async save() { if (this.getIsNew()) { - await this.repository.create(this.#data.value); + if ((await this.structure.create()) === true) { + this.setIsNew(false); + } } else { - await this.repository.save(this.#data.value.id, this.#data.value); + await this.structure.save(); } - this.saveComplete(this.#data.value); + this.saveComplete(this.getData()); } public destroy(): void { - this.#data.destroy(); + this.structure.destroy(); + super.destroy(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-properties.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-properties.element.ts new file mode 100644 index 0000000000..10ea5b0184 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-properties.element.ts @@ -0,0 +1,214 @@ +import { UmbMediaTypeWorkspaceContext } from '../../media-type-workspace.context.js'; +import './media-type-workspace-view-edit-property.element.js'; +import { css, html, customElement, property, state, repeat, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbContentTypePropertyStructureHelper, PropertyContainerTypes } from '@umbraco-cms/backoffice/content-type'; +import { UmbSorterController, UmbSorterConfig } from '@umbraco-cms/backoffice/sorter'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { + MediaTypePropertyTypeResponseModel, + MediaTypeResponseModel, + PropertyTypeModelBaseModel, +} from '@umbraco-cms/backoffice/backend-api'; +import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; +import { UMB_PROPERTY_SETTINGS_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal'; + +const SORTER_CONFIG: UmbSorterConfig = { + compareElementToModel: (element: HTMLElement, model: MediaTypePropertyTypeResponseModel) => { + return element.getAttribute('data-umb-property-id') === model.id; + }, + querySelectModelToElement: (container: HTMLElement, modelEntry: MediaTypePropertyTypeResponseModel) => { + return container.querySelector('data-umb-property-id=[' + modelEntry.id + ']'); + }, + identifier: 'content-type-property-sorter', + itemSelector: '[data-umb-property-id]', + disabledItemSelector: '[inherited]', + containerSelector: '#property-list', +}; + +@customElement('umb-media-type-workspace-view-edit-properties') +export class UmbMediaTypeWorkspaceViewEditPropertiesElement extends UmbLitElement { + #propertySorter = new UmbSorterController(this, { + ...SORTER_CONFIG, + performItemInsert: (args) => { + let sortOrder = 0; + if (this._propertyStructure.length > 0) { + if (args.newIndex === 0) { + sortOrder = (this._propertyStructure[0].sortOrder ?? 0) - 1; + } else { + sortOrder = + (this._propertyStructure[Math.min(args.newIndex, this._propertyStructure.length - 1)].sortOrder ?? 0) + 1; + } + } + return this._propertyStructureHelper.insertProperty(args.item, sortOrder); + }, + performItemRemove: (args) => { + return this._propertyStructureHelper.removeProperty(args.item.id!); + }, + }); + + private _containerId: string | undefined; + + @property({ type: String, attribute: 'container-id', reflect: false }) + public get containerId(): string | undefined { + return this._containerId; + } + public set containerId(value: string | undefined) { + if (value === this._containerId) return; + const oldValue = this._containerId; + this._containerId = value; + this.requestUpdate('containerId', oldValue); + } + + @property({ type: String, attribute: 'container-name', reflect: false }) + public get containerName(): string | undefined { + return this._propertyStructureHelper.getContainerName(); + } + public set containerName(value: string | undefined) { + this._propertyStructureHelper.setContainerName(value); + } + + @property({ type: String, attribute: 'container-type', reflect: false }) + public get containerType(): PropertyContainerTypes | undefined { + return this._propertyStructureHelper.getContainerType(); + } + public set containerType(value: PropertyContainerTypes | undefined) { + this._propertyStructureHelper.setContainerType(value); + } + + _propertyStructureHelper = new UmbContentTypePropertyStructureHelper(this); + + @state() + _propertyStructure: Array = []; + + @state() + _ownerMediaTypes?: MediaTypeResponseModel[]; + + @state() + protected _modalRouteNewProperty?: string; + + @state() + _sortModeActive?: boolean; + + constructor() { + super(); + + this.consumeContext(UMB_WORKSPACE_CONTEXT, (workspaceContext) => { + this._propertyStructureHelper.setStructureManager((workspaceContext as UmbMediaTypeWorkspaceContext).structure); + this.observe( + (workspaceContext as UmbMediaTypeWorkspaceContext).isSorting, + (isSorting) => { + this._sortModeActive = isSorting; + this.#setModel(isSorting); + }, + '_observeIsSorting', + ); + }); + this.observe(this._propertyStructureHelper.propertyStructure, (propertyStructure) => { + this._propertyStructure = propertyStructure; + }); + + // Note: Route for adding a new property + new UmbModalRouteRegistrationController(this, UMB_PROPERTY_SETTINGS_MODAL) + .addAdditionalPath('new-property') + .onSetup(async () => { + const mediaTypeId = this._ownerMediaTypes?.find( + (types) => types.containers?.find((containers) => containers.id === this.containerId), + )?.id; + if (mediaTypeId === undefined) return false; + const propertyData = await this._propertyStructureHelper.createPropertyScaffold(this._containerId); + if (propertyData === undefined) return false; + return { propertyData, documentTypeId: mediaTypeId }; //TODO: Should we have a separate modal for mediaTypes? + }) + .onSubmit((result) => { + this.#addProperty(result); + }) + .observeRouteBuilder((routeBuilder) => { + this._modalRouteNewProperty = routeBuilder(null); + }); + } + + #setModel(isSorting?: boolean) { + if (isSorting) { + this.#propertySorter.setModel(this._propertyStructure); + } else { + this.#propertySorter.setModel([]); + } + } + + connectedCallback(): void { + super.connectedCallback(); + const mediaTypes = this._propertyStructureHelper.ownerDocumentTypes; //TODO: Should we have a separate propertyStructureHelper for mediaTypes? + if (!mediaTypes) return; + this.observe( + mediaTypes, + (medias) => { + this._ownerMediaTypes = medias; + }, + 'observeOwnerMediaTypes', + ); + } + + async #addProperty(propertyData: PropertyTypeModelBaseModel) { + const propertyPlaceholder = await this._propertyStructureHelper.addProperty(this._containerId); + if (!propertyPlaceholder) return; + + this._propertyStructureHelper.partialUpdateProperty(propertyPlaceholder.id, propertyData); + } + + render() { + return html`
+ ${repeat( + this._propertyStructure, + (property) => property.id ?? '' + property.containerId ?? '' + property.sortOrder ?? '', + (property) => { + // Note: This piece might be moved into the property component + const inheritedFromMedia = this._ownerMediaTypes?.find( + (types) => types.containers?.find((containers) => containers.id === property.containerId), + ); + + return html` { + this._propertyStructureHelper.partialUpdateProperty(property.id, event.detail); + }} + @property-delete=${() => { + this._propertyStructureHelper.removeProperty(property.id!); + }}> + `; + }, + )} +
+ ${!this._sortModeActive + ? html` + Add property + ` + : ''} `; + } + + static styles = [ + UmbTextStyles, + css` + #add { + width: 100%; + } + `, + ]; +} + +export default UmbMediaTypeWorkspaceViewEditPropertiesElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-media-type-workspace-view-edit-properties': UmbMediaTypeWorkspaceViewEditPropertiesElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-property.element.ts new file mode 100644 index 0000000000..2bcdd2e19f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-property.element.ts @@ -0,0 +1,478 @@ +import { UmbDataTypeDetailRepository } from '@umbraco-cms/backoffice/data-type'; +import { UUIInputElement, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; +import { css, html, customElement, property, state, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit'; +import { PropertyTypeModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; +import { + UMB_CONFIRM_MODAL, + UMB_MODAL_MANAGER_CONTEXT_TOKEN, + UMB_PROPERTY_SETTINGS_MODAL, + UMB_WORKSPACE_MODAL, + UmbConfirmModalData, + UmbModalRouteRegistrationController, +} from '@umbraco-cms/backoffice/modal'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { generateAlias } from '@umbraco-cms/backoffice/utils'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; + +/** + * @element media-type-workspace-view-edit-property + * @description - Element for displaying a property in an workspace. + * @slot editor - Slot for rendering the Property Editor + */ +@customElement('media-type-workspace-view-edit-property') +export class UmbMediaTypeWorkspacePropertyElement extends UmbLitElement { + private _property?: PropertyTypeModelBaseModel | undefined; + /** + * Property, the data object for the property. + * @type {PropertyTypeModelBaseModel} + * @attr + * @default undefined + */ + @property({ type: Object }) + public get property(): PropertyTypeModelBaseModel | undefined { + return this._property; + } + public set property(value: PropertyTypeModelBaseModel | undefined) { + const oldValue = this._property; + this._property = value; + this.#modalRegistration.setUniquePathValue('propertyId', value?.id?.toString()); + this.setDataType(this._property?.dataTypeId); + this.requestUpdate('property', oldValue); + } + + /** + * Inherited, Determines if the property is part of the main media type thats being edited. + * If true, then the property is inherited from another media type, not a part of the main media type. + * @type {boolean} + * @attr + * @default undefined + */ + @property({ type: Boolean }) + public inherited?: boolean; + + @property({ type: Boolean, reflect: true, attribute: 'sort-mode-active' }) + public sortModeActive = false; + + #dataTypeDetailRepository = new UmbDataTypeDetailRepository(this); + + #modalRegistration; + private _modalManagerContext?: typeof UMB_MODAL_MANAGER_CONTEXT_TOKEN.TYPE; + + @state() + protected _modalRoute?: string; + + @state() + protected _editMediaTypePath?: string; + + @property() + public get modalRoute() { + return this._modalRoute; + } + + @property({ type: String, attribute: 'owner-media-type-id' }) + public ownerMediaTypeId?: string; + + @property({ type: String, attribute: 'owner-media-type-name' }) + public ownerMediaTypeName?: string; + + @state() + private _dataTypeName?: string; + + async setDataType(dataTypeId: string | undefined) { + if (!dataTypeId) return; + this.#dataTypeDetailRepository.requestById(dataTypeId).then((x) => (this._dataTypeName = x?.data?.name)); + } + + constructor() { + super(); + this.#modalRegistration = new UmbModalRouteRegistrationController(this, UMB_PROPERTY_SETTINGS_MODAL) + .addUniquePaths(['propertyId']) + .onSetup(() => { + const mediaTypeId = this.ownerMediaTypeId; + if (mediaTypeId === undefined) return false; + const propertyData = this.property; + if (propertyData === undefined) return false; + return { propertyData, documentTypeId: mediaTypeId }; //TODO: Should we have a separate modal for mediaTypes? + }) + .onSubmit((result) => { + this._partialUpdate(result); + }) + .observeRouteBuilder((routeBuilder) => { + this._modalRoute = routeBuilder(null); + }); + + new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) + .addAdditionalPath('media-type') + .onSetup(() => { + return { entityType: 'media-type', preset: {} }; + }) + .observeRouteBuilder((routeBuilder) => { + this._editMediaTypePath = routeBuilder({}); + }); + + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (context) => { + this._modalManagerContext = context; + }); + } + + _partialUpdate(partialObject: PropertyTypeModelBaseModel) { + this.dispatchEvent(new CustomEvent('partial-property-update', { detail: partialObject })); + } + + _singleValueUpdate(propertyName: string, value: string | number | boolean | null | undefined) { + const partialObject = {} as any; + partialObject[propertyName] = value; + + this.dispatchEvent(new CustomEvent('partial-property-update', { detail: partialObject })); + } + + @state() + private _aliasLocked = true; + + #onToggleAliasLock() { + this._aliasLocked = !this._aliasLocked; + } + + #requestRemove(e: Event) { + e.preventDefault(); + e.stopImmediatePropagation(); + if (!this.property || !this.property.id) return; + + const Message: UmbConfirmModalData = { + headline: `${this.localize.term('actions_delete')} property`, + content: html` + Are you sure you want to delete the property ${this.property.name || this.property.id} + + `, + confirmLabel: this.localize.term('actions_delete'), + color: 'danger', + }; + + const modalHandler = this._modalManagerContext?.open(UMB_CONFIRM_MODAL, Message); + + modalHandler + ?.onSubmit() + .then(() => { + this.dispatchEvent(new CustomEvent('property-delete')); + }) + .catch(() => { + // We do not need to react to cancel, so we will leave an empty method to prevent Uncaught Promise Rejection error. + return; + }); + } + + #onNameChange(event: UUIInputEvent) { + if (event instanceof UUIInputEvent) { + const target = event.composedPath()[0] as UUIInputElement; + + if (typeof target?.value === 'string') { + const oldName = this.property?.name ?? ''; + const oldAlias = this.property?.alias ?? ''; + const newName = event.target.value.toString(); + if (this._aliasLocked) { + const expectedOldAlias = generateAlias(oldName ?? ''); + // Only update the alias if the alias matches a generated alias of the old name (otherwise the alias is considered one written by the user.) + if (expectedOldAlias === oldAlias) { + this._singleValueUpdate('alias', generateAlias(newName ?? '')); + } + } + this._singleValueUpdate('name', newName); + } + } + } + + renderSortableProperty() { + if (!this.property) return; + return html` +
+ + ${this.property.name} (${this.property.alias}) +
+ + `; + } + + renderEditableProperty() { + if (!this.property) return; + + if (this.sortModeActive) { + return this.renderSortableProperty(); + } else { + return html` + + + ${this.renderPropertyTags()} + + + + + + + `; + } + } + + renderInheritedProperty() { + if (!this.property) return; + + if (this.sortModeActive) { + return this.renderSortableProperty(); + } else { + return html` + +
+ ${this.renderPropertyTags()} + + + ${this.localize.term('contentTypeEditor_inheritedFrom')} + + ${this.ownerMediaTypeName ?? '??'} + + + +
+ `; + } + } + + renderPropertyAlias() { + return this.property + ? html` { + if (e.target) this._singleValueUpdate('alias', (e.target as HTMLInputElement).value); + }}> + + +
''} id="alias-lock" slot="prepend"> + +
+
` + : ''; + } + + renderPropertyTags() { + return this.property + ? html`
+ ${this.property.dataTypeId ? html`${this._dataTypeName}` : nothing} + ${this.property.variesByCulture + ? html` + ${this.localize.term('contentTypeEditor_cultureVariantLabel')} + ` + : nothing} + ${this.property.appearance?.labelOnTop == true + ? html` + ${this.localize.term('contentTypeEditor_displaySettingsLabelOnTop')} + ` + : nothing} +
` + : nothing; + } + + render() { + // TODO: Only show alias on label if user has access to MediaType within settings: + return this.inherited ? this.renderInheritedProperty() : this.renderEditableProperty(); + } + + static styles = [ + UmbTextStyles, + css` + :host(:not([sort-mode-active])) { + display: grid; + grid-template-columns: 200px auto; + column-gap: var(--uui-size-layout-2); + border-bottom: 1px solid var(--uui-color-divider); + padding: var(--uui-size-layout-1) 0; + container-type: inline-size; + } + + :host > div { + grid-column: span 2; + } + + @container (width > 600px) { + :host(:not([orientation='vertical'])) > div { + grid-column: span 1; + } + } + + :host(:first-of-type) { + padding-top: 0; + } + :host(:last-of-type) { + border-bottom: none; + } + + :host([sort-mode-active]) { + position: relative; + display: flex; + padding: 0; + margin-bottom: var(--uui-size-3); + } + + :host([sort-mode-active]:last-of-type) { + margin-bottom: 0; + } + + :host([sort-mode-active]:not([inherited])) { + cursor: grab; + } + + :host([sort-mode-active]) .sortable { + flex: 1; + display: flex; + background-color: var(--uui-color-divider); + align-items: center; + padding: 0 var(--uui-size-3); + gap: var(--uui-size-3); + } + + :host([sort-mode-active]) uui-input { + max-width: 75px; + } + + /* Placeholder style, used when property is being dragged.*/ + :host(.--umb-sorter-placeholder) > * { + visibility: hidden; + } + + :host(.--umb-sorter-placeholder)::after { + content: ''; + inset: 0; + position: absolute; + border: 1px dashed var(--uui-color-divider-emphasis); + border-radius: var(--uui-border-radius); + } + + p { + margin-bottom: 0; + } + + #header { + position: sticky; + top: var(--uui-size-space-4); + height: min-content; + z-index: 2; + } + + #editor { + position: relative; + background-color: var(--uui-color-background); + } + #alias-input, + #label-input, + #description-input { + width: 100%; + } + + #alias-input { + border-color: transparent; + background: var(--uui-color-surface); + } + + #label-input { + font-weight: bold; /* TODO: UUI Input does not support bold text yet */ + --uui-input-border-color: transparent; + } + #label-input input { + font-weight: bold; + --uui-input-border-color: transparent; + } + + #alias-lock { + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + } + #alias-lock uui-icon { + margin-bottom: 2px; + /* margin: 0; */ + } + #description-input { + --uui-textarea-border-color: transparent; + font-weight: 0.5rem; /* TODO: Cant change font size of UUI textarea yet */ + } + + .types > div uui-icon, + .inherited uui-icon { + vertical-align: sub; + } + + .inherited { + position: absolute; + top: var(--uui-size-space-2); + right: var(--uui-size-space-2); + } + + .types { + position: absolute; + top: var(--uui-size-space-2); + left: var(--uui-size-space-2); + display: flex; + gap: var(--uui-size-space-2); + } + + #editor uui-action-bar { + position: absolute; + top: var(--uui-size-space-2); + right: var(--uui-size-space-2); + display: none; + } + #editor:hover uui-action-bar, + #editor:focus uui-action-bar { + display: block; + } + + a { + color: inherit; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + 'media-type-workspace-view-edit-property': UmbMediaTypeWorkspacePropertyElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-tab.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-tab.element.ts new file mode 100644 index 0000000000..fd56eccc7e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-tab.element.ts @@ -0,0 +1,272 @@ +import { UmbMediaTypeWorkspaceContext } from '../../media-type-workspace.context.js'; +import { css, html, customElement, property, state, repeat, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbContentTypeContainerStructureHelper } from '@umbraco-cms/backoffice/content-type'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { PropertyTypeContainerModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; +import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; +import { UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter'; + +import './media-type-workspace-view-edit-properties.element.js'; + +const SORTER_CONFIG: UmbSorterConfig = { + compareElementToModel: (element: HTMLElement, model: PropertyTypeContainerModelBaseModel) => { + return element.getAttribute('data-umb-group-id') === model.id; + }, + querySelectModelToElement: (container: HTMLElement, modelEntry: PropertyTypeContainerModelBaseModel) => { + return container.querySelector('data-umb-group-id=[' + modelEntry.id + ']'); + }, + identifier: 'content-type-group-sorter', + itemSelector: '[data-umb-group-id]', + disabledItemSelector: '[inherited]', + containerSelector: '#group-list', +}; + +@customElement('umb-media-type-workspace-view-edit-tab') +export class UmbMediaTypeWorkspaceViewEditTabElement extends UmbLitElement { + public sorter?: UmbSorterController; + + config: UmbSorterConfig = { + ...SORTER_CONFIG, + performItemInsert: async (args) => { + if (!this._groups) return false; + const oldIndex = this._groups.findIndex((group) => group.id! === args.item.id); + if (args.newIndex === oldIndex) return true; + + let sortOrder = 0; + //TODO the sortOrder set is not correct + if (this._groups.length > 0) { + if (args.newIndex === 0) { + sortOrder = (this._groups[0].sortOrder ?? 0) - 1; + } else { + sortOrder = (this._groups[Math.min(args.newIndex, this._groups.length - 1)].sortOrder ?? 0) + 1; + } + + if (sortOrder !== args.item.sortOrder) { + await this._groupStructureHelper.partialUpdateContainer(args.item.id!, { sortOrder }); + } + } + + return true; + }, + }; + + private _ownerTabId?: string | null; + + // TODO: get rid of this: + @property({ type: String }) + public get ownerTabId(): string | null | undefined { + return this._ownerTabId; + } + public set ownerTabId(value: string | null | undefined) { + if (value === this._ownerTabId) return; + const oldValue = this._ownerTabId; + this._ownerTabId = value; + this._groupStructureHelper.setOwnerId(value); + this.requestUpdate('ownerTabId', oldValue); + } + + private _tabName?: string | undefined; + + @property({ type: String }) + public get tabName(): string | undefined { + return this._groupStructureHelper.getName(); + } + public set tabName(value: string | undefined) { + if (value === this._tabName) return; + const oldValue = this._tabName; + this._tabName = value; + this._groupStructureHelper.setName(value); + this.requestUpdate('tabName', oldValue); + } + + @state() + private _noTabName?: boolean; + + @property({ type: Boolean }) + public get noTabName(): boolean { + return this._groupStructureHelper.getIsRoot(); + } + public set noTabName(value: boolean) { + this._noTabName = value; + this._groupStructureHelper.setIsRoot(value); + } + + _groupStructureHelper = new UmbContentTypeContainerStructureHelper(this); + + @state() + _groups: Array = []; + + @state() + _hasProperties = false; + + @state() + _sortModeActive?: boolean; + + constructor() { + super(); + + this.sorter = new UmbSorterController(this, this.config); + + this.consumeContext(UMB_WORKSPACE_CONTEXT, (context) => { + this._groupStructureHelper.setStructureManager((context as UmbMediaTypeWorkspaceContext).structure); + this.observe( + (context as UmbMediaTypeWorkspaceContext).isSorting, + (isSorting) => { + this._sortModeActive = isSorting; + if (isSorting) { + this.sorter?.setModel(this._groups); + } else { + this.sorter?.setModel([]); + } + }, + '_observeIsSorting', + ); + }); + this.observe(this._groupStructureHelper.containers, (groups) => { + this._groups = groups; + this.requestUpdate('_groups'); + }); + this.observe(this._groupStructureHelper.hasProperties, (hasProperties) => { + this._hasProperties = hasProperties; + this.requestUpdate('_hasProperties'); + }); + } + + #onAddGroup = () => { + // Idea, maybe we can gather the sortOrder from the last group rendered and add 1 to it? + this._groupStructureHelper.addContainer(this._ownerTabId); + }; + + render() { + return html` + ${!this._noTabName + ? html` + + + + ` + : ''} +
+ ${repeat( + this._groups, + (group) => group.id ?? '' + group.name, + (group) => html` + + ${ + this._groupStructureHelper.isOwnerChildContainer(group.id!) + ? html` +
+
+ ${this._sortModeActive ? html`` : ''} + + { + const newName = (e.target as HTMLInputElement).value; + this._groupStructureHelper.updateContainerName(group.id!, group.parentId ?? null, newName); + }}> + +
+ ${this._sortModeActive + ? html`` + : ''} +
+ ` + : html`
+
${group.name ?? ''} (Inherited)
+ ${!this._sortModeActive + ? html`` + : ''} +
` + } +
+ + `, + )} + + ${!this._sortModeActive + ? html` + ${this.localize.term('contentTypeEditor_addGroup')} + ` + : ''} + `; + } + + static styles = [ + UmbTextStyles, + css` + #add { + width: 100%; + } + + #add:first-child { + margin-top: var(--uui-size-layout-1); + } + uui-box { + margin-bottom: var(--uui-size-layout-1); + } + + [data-umb-group-id] { + display: block; + position: relative; + } + + div[slot='header'] { + display: flex; + align-items: center; + justify-content: space-between; + } + + div[slot='header'] > div { + display: flex; + align-items: center; + gap: var(--uui-size-3); + } + + uui-input[type='number'] { + max-width: 75px; + } + + .sorting { + cursor: grab; + } + + .--umb-sorter-placeholder > uui-box { + visibility: hidden; + } + + .--umb-sorter-placeholder::after { + content: ''; + inset: 0; + position: absolute; + border-radius: var(--uui-border-radius); + border: 1px dashed var(--uui-color-divider-emphasis); + } + `, + ]; +} + +export default UmbMediaTypeWorkspaceViewEditTabElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-media-type-workspace-view-edit-tab': UmbMediaTypeWorkspaceViewEditTabElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts new file mode 100644 index 0000000000..ec8bb69c98 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts @@ -0,0 +1,518 @@ +import { UmbMediaTypeWorkspaceContext } from '../../media-type-workspace.context.js'; +import type { UmbMediaTypeWorkspaceViewEditTabElement } from './media-type-workspace-view-edit-tab.element.js'; +import { css, html, customElement, state, repeat, nothing, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import { UUIInputElement, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; +import { UmbContentTypeContainerStructureHelper } from '@umbraco-cms/backoffice/content-type'; +import { encodeFolderName, UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { + MediaTypePropertyTypeContainerResponseModel, + PropertyTypeContainerModelBaseModel, +} from '@umbraco-cms/backoffice/backend-api'; +import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; +import type { UmbRoute } from '@umbraco-cms/backoffice/router'; +import { UmbWorkspaceEditorViewExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; +import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT_TOKEN, UmbConfirmModalData } from '@umbraco-cms/backoffice/modal'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter'; + +const SORTER_CONFIG: UmbSorterConfig = { + compareElementToModel: (element: HTMLElement, model: MediaTypePropertyTypeContainerResponseModel) => { + return element.getAttribute('data-umb-tabs-id') === model.id; + }, + querySelectModelToElement: (container: HTMLElement, modelEntry: PropertyTypeContainerModelBaseModel) => { + return container.querySelector(`[data-umb-tabs-id='` + modelEntry.id + `']`); + }, + identifier: 'content-type-tabs-sorter', + itemSelector: '[data-umb-tabs-id]', + containerSelector: '#tabs-group', + disabledItemSelector: '[inherited]', + resolveVerticalDirection: () => { + return false; + }, +}; + +@customElement('umb-media-type-workspace-view-edit') +export class UmbMediaTypeWorkspaceViewEditElement + extends UmbLitElement + implements UmbWorkspaceEditorViewExtensionElement +{ + public sorter?: UmbSorterController; + + config: UmbSorterConfig = { + ...SORTER_CONFIG, + performItemInsert: async (args) => { + if (!this._tabs) return false; + const oldIndex = this._tabs.findIndex((tab) => tab.id! === args.item.id); + if (args.newIndex === oldIndex) return true; + + let sortOrder = 0; + //TODO the sortOrder set is not correct + if (this._tabs.length > 0) { + if (args.newIndex === 0) { + sortOrder = (this._tabs[0].sortOrder ?? 0) - 1; + } else { + sortOrder = (this._tabs[Math.min(args.newIndex, this._tabs.length - 1)].sortOrder ?? 0) + 1; + } + + if (sortOrder !== args.item.sortOrder) { + await this._tabsStructureHelper.partialUpdateContainer(args.item.id!, { sortOrder }); + } + } + + return true; + }, + }; + + //private _hasRootProperties = false; + private _hasRootGroups = false; + + @state() + private _routes: UmbRoute[] = []; + + @state() + _tabs?: Array; + + @state() + private _routerPath?: string; + + @state() + private _activePath = ''; + + @state() + private sortModeActive?: boolean; + + @state() + private _buttonDisabled: boolean = false; + + private _workspaceContext?: UmbMediaTypeWorkspaceContext; + + private _tabsStructureHelper = new UmbContentTypeContainerStructureHelper(this); + + private _modalManagerContext?: typeof UMB_MODAL_MANAGER_CONTEXT_TOKEN.TYPE; + + constructor() { + super(); + this.sorter = new UmbSorterController(this, this.config); + + //TODO: We need to differentiate between local and composition tabs (and hybrids) + + this._tabsStructureHelper.setIsRoot(true); + this._tabsStructureHelper.setContainerChildType('Tab'); + this.observe(this._tabsStructureHelper.containers, (tabs) => { + this._tabs = tabs; + this._createRoutes(); + }); + + // _hasRootProperties can be gotten via _tabsStructureHelper.hasProperties. But we do not support root properties currently. + + this.consumeContext(UMB_WORKSPACE_CONTEXT, (workspaceContext) => { + this._workspaceContext = workspaceContext as UmbMediaTypeWorkspaceContext; + this._tabsStructureHelper.setStructureManager((workspaceContext as UmbMediaTypeWorkspaceContext).structure); + this.observe( + this._workspaceContext.isSorting, + (isSorting) => (this.sortModeActive = isSorting), + '_observeIsSorting', + ); + this._observeRootGroups(); + }); + + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (context) => { + this._modalManagerContext = context; + }); + } + + private _observeRootGroups() { + if (!this._workspaceContext) return; + + this.observe( + this._workspaceContext.structure.hasRootContainers('Group'), + (hasRootGroups) => { + this._hasRootGroups = hasRootGroups; + this._createRoutes(); + }, + '_observeGroups', + ); + } + + #changeMode() { + this._workspaceContext?.setIsSorting(!this.sortModeActive); + + if (this.sortModeActive && this._tabs) { + this.sorter?.setModel(this._tabs); + } else { + this.sorter?.setModel([]); + } + } + + private _createRoutes() { + if (!this._workspaceContext || !this._tabs) return; + const routes: UmbRoute[] = []; + + if (this._tabs.length > 0) { + this._tabs?.forEach((tab) => { + const tabName = tab.name ?? ''; + routes.push({ + path: `tab/${encodeFolderName(tabName).toString()}`, + component: () => import('./media-type-workspace-view-edit-tab.element.js'), + setup: (component) => { + (component as UmbMediaTypeWorkspaceViewEditTabElement).tabName = tabName; + (component as UmbMediaTypeWorkspaceViewEditTabElement).ownerTabId = + this._workspaceContext?.structure.isOwnerContainer(tab.id!) ? tab.id : undefined; + }, + }); + }); + } + + routes.push({ + path: 'root', + component: () => import('./media-type-workspace-view-edit-tab.element.js'), + setup: (component) => { + (component as UmbMediaTypeWorkspaceViewEditTabElement).noTabName = true; + (component as UmbMediaTypeWorkspaceViewEditTabElement).ownerTabId = null; + }, + }); + + if (this._hasRootGroups) { + routes.push({ + path: '', + redirectTo: 'root', + }); + } else if (routes.length !== 0) { + routes.push({ + path: '', + redirectTo: routes[0]?.path, + }); + } + + this._routes = routes; + } + + #requestRemoveTab(tab: PropertyTypeContainerModelBaseModel | undefined) { + const Message: UmbConfirmModalData = { + headline: 'Delete tab', + content: html` + Are you sure you want to delete the tab ${tab?.name ?? tab?.id} + +
+ + This will delete all items that doesn't belong to a composition. + +
`, + confirmLabel: this.localize.term('actions_delete'), + color: 'danger', + }; + + // TODO: If this tab is composed of other tabs, then notify that it will only delete the local tab. + + const modalHandler = this._modalManagerContext?.open(UMB_CONFIRM_MODAL, Message); + + modalHandler?.onSubmit().then(() => { + this.#remove(tab?.id); + }); + } + #remove(tabId?: string) { + if (!tabId) return; + this._workspaceContext?.structure.removeContainer(null, tabId); + this._tabsStructureHelper?.isOwnerContainer(tabId) + ? window.history.replaceState(null, '', this._routerPath + this._routes[0]?.path ?? '/root') + : ''; + } + async #addTab() { + if ( + (this.shadowRoot?.querySelector('uui-tab[active] uui-input') as UUIInputElement) && + (this.shadowRoot?.querySelector('uui-tab[active] uui-input') as UUIInputElement).value === '' + ) { + this.#focusInput(); + return; + } + + const tab = await this._workspaceContext?.structure.createContainer(null, null, 'Tab'); + if (tab) { + const path = this._routerPath + '/tab/' + encodeFolderName(tab.name || ''); + window.history.replaceState(null, '', path); + this.#focusInput(); + } + } + + async #focusInput() { + setTimeout(() => { + (this.shadowRoot?.querySelector('uui-tab[active] uui-input') as UUIInputElement | undefined)?.focus(); + }, 100); + } + + async #tabNameChanged(event: InputEvent, tab: PropertyTypeContainerModelBaseModel) { + if (this._buttonDisabled) this._buttonDisabled = !this._buttonDisabled; + let newName = (event.target as HTMLInputElement).value; + + if (newName === '') { + newName = 'Unnamed'; + (event.target as HTMLInputElement).value = 'Unnamed'; + } + + const changedName = this._workspaceContext?.structure.makeContainerNameUniqueForOwnerContentType( + newName, + 'Tab', + tab.id, + ); + + // Check if it collides with another tab name of this same media-type, if so adjust name: + if (changedName) { + newName = changedName; + (event.target as HTMLInputElement).value = newName; + } + + this._tabsStructureHelper.partialUpdateContainer(tab.id!, { + name: newName, + }); + + // Update the current URL, so we are still on this specific tab: + window.history.replaceState(null, '', this._routerPath + '/tab/' + encodeFolderName(newName)); + } + + render() { + return html` + + + { + this._routerPath = event.target.absoluteRouterPath; + }} + @change=${(event: UmbRouterSlotChangeEvent) => { + this._activePath = event.target.absoluteActiveViewPath || ''; + }}> + + + `; + } + + renderAddButton() { + if (this.sortModeActive) return; + return html` + + Add tab + `; + } + + renderActions() { + const sortButtonText = this.sortModeActive + ? this.localize.term('general_reorderDone') + : this.localize.term('general_reorder'); + + return html`
+ + + ${this.localize.term('contentTypeEditor_compositions')} + + + + ${sortButtonText} + +
`; + } + + renderTabsNavigation() { + if (!this._tabs) return; + + return html`
+ + ${this.renderRootTab()} + ${repeat( + this._tabs, + (tab) => tab.id! + tab.name, + (tab) => this.renderTab(tab), + )} + +
`; + } + + renderRootTab() { + const rootTabPath = this._routerPath + '/root'; + const rootTabActive = rootTabPath === this._activePath; + return html` + ${this.localize.term('general_content')} + `; + } + + renderTab(tab: PropertyTypeContainerModelBaseModel) { + const path = this._routerPath + '/tab/' + encodeFolderName(tab.name || ''); + const tabActive = path === this._activePath; + const tabInherited = !this._tabsStructureHelper.isOwnerContainer(tab.id!); + + return html` + ${this.renderTabInner(tab, tabActive, tabInherited)} + `; + } + + renderTabInner(tab: PropertyTypeContainerModelBaseModel, tabActive: boolean, tabInherited: boolean) { + if (this.sortModeActive) { + return html`
+ ${tabInherited + ? html`${tab.name!}` + : html` ${tab.name!} + this.#changeOrderNumber(tab, e)}>`} +
`; + } + + if (tabActive && !tabInherited) { + return html`
+ this.#tabNameChanged(e, tab)} + @blur=${(e: InputEvent) => this.#tabNameChanged(e, tab)} + @input=${() => (this._buttonDisabled = true)} + @focus=${(e: UUIInputEvent) => (e.target.value ? nothing : (this._buttonDisabled = true))}> + ${this.renderDeleteFor(tab)} + +
`; + } + + if (tabInherited) { + return html`
${tab.name!}
`; + } else { + return html`
${tab.name!} ${this.renderDeleteFor(tab)}
`; + } + } + + #changeOrderNumber(tab: PropertyTypeContainerModelBaseModel, e: UUIInputEvent) { + if (!e.target.value || !tab.id) return; + const sortOrder = Number(e.target.value); + this._tabsStructureHelper.partialUpdateContainer(tab.id, { sortOrder }); + } + + renderDeleteFor(tab: PropertyTypeContainerModelBaseModel) { + return html` this.#requestRemoveTab(tab)} + compact> + + `; + } + + static styles = [ + UmbTextStyles, + css` + #buttons-wrapper { + flex: 1; + display: flex; + align-items: center; + justify-content: space-between; + align-items: stretch; + } + + :host { + position: relative; + display: flex; + flex-direction: column; + height: 100%; + --uui-tab-background: var(--uui-color-surface); + } + + /* TODO: This should be replaced with a general workspace bar — naming is hard */ + #header { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: nowrap; + } + + .flex { + display: flex; + } + uui-tab-group { + flex-wrap: nowrap; + } + + .content-tab-is-empty { + align-self: center; + border-radius: 3px; + --uui-tab-text: var(--uui-color-text-alt); + border: dashed 1px var(--uui-color-border-emphasis); + } + + uui-tab { + position: relative; + border-left: 1px hidden transparent; + border-right: 1px solid var(--uui-color-border); + } + + .no-edit uui-input { + pointer-events: auto; + } + + .no-edit { + pointer-events: none; + display: inline-flex; + padding-left: var(--uui-size-space-3); + border: 1px solid transparent; + align-items: center; + gap: var(--uui-size-space-3); + } + + .trash { + opacity: 1; + transition: opacity 120ms; + } + + uui-tab:not(:hover, :focus) .trash { + opacity: 0; + transition: opacity 120ms; + } + + uui-input:not(:focus, :hover) { + border: 1px solid transparent; + } + + .inherited { + vertical-align: sub; + } + + .--umb-sorter-placeholder > * { + visibility: hidden; + } + + .--umb-sorter-placeholder::after { + content: ''; + position: absolute; + inset: 2px; + border: 1px dashed var(--uui-color-divider-emphasis); + } + `, + ]; +} + +export default UmbMediaTypeWorkspaceViewEditElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-media-type-workspace-view-edit': UmbMediaTypeWorkspaceViewEditElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/details/media-type-design-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/details/media-type-design-workspace-view.element.ts deleted file mode 100644 index 77b9b97f99..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/details/media-type-design-workspace-view.element.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { UMB_MEDIA_TYPE_WORKSPACE_CONTEXT } from '../../media-type-workspace.context.js'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { css, html, nothing, customElement, state } from '@umbraco-cms/backoffice/external/lit'; -import { - UmbModalManagerContext, - UMB_MODAL_MANAGER_CONTEXT_TOKEN, - UMB_PROPERTY_EDITOR_UI_PICKER_MODAL, -} from '@umbraco-cms/backoffice/modal'; -import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { MediaTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; -import { UmbWorkspaceEditorViewExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; - -@customElement('umb-media-type-design-workspace-view') -export class UmbMediaTypeDesignWorkspaceViewEditElement - extends UmbLitElement - implements UmbWorkspaceEditorViewExtensionElement -{ - @state() - _mediaType?: MediaTypeResponseModel; - - private _workspaceContext?: typeof UMB_MEDIA_TYPE_WORKSPACE_CONTEXT.TYPE; - - constructor() { - super(); - - this.consumeContext(UMB_MEDIA_TYPE_WORKSPACE_CONTEXT, (_instance) => { - this._workspaceContext = _instance; - this._observeMediaType(); - }); - } - - private _observeMediaType() { - if (!this._workspaceContext) { - return; - } - - this.observe(this._workspaceContext.data, (mediaType) => { - this._mediaType = mediaType; - }); - } - - render() { - return html` ${this._mediaType?.alias}`; - } - - static styles = [ - UmbTextStyles, - css` - :host { - display: block; - margin: var(--uui-size-layout-1); - padding-bottom: var(--uui-size-layout-1); - } - - uui-box { - margin-top: var(--uui-size-layout-1); - } - `, - ]; -} - -export default UmbMediaTypeDesignWorkspaceViewEditElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-media-type-design-workspace-view': UmbMediaTypeDesignWorkspaceViewEditElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/details/media-type-list-view-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/details/media-type-list-view-workspace-view.element.ts deleted file mode 100644 index 46a63c562c..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/details/media-type-list-view-workspace-view.element.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { UMB_MEDIA_TYPE_WORKSPACE_CONTEXT } from '../../media-type-workspace.context.js'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { css, html, nothing, customElement, state } from '@umbraco-cms/backoffice/external/lit'; -import { - UmbModalManagerContext, - UMB_MODAL_MANAGER_CONTEXT_TOKEN, - UMB_PROPERTY_EDITOR_UI_PICKER_MODAL, -} from '@umbraco-cms/backoffice/modal'; -import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { MediaTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; -import { UmbWorkspaceEditorViewExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; - -@customElement('umb-media-type-list-view-workspace-view') -export class UmbMediaTypeListViewWorkspaceViewEditElement - extends UmbLitElement - implements UmbWorkspaceEditorViewExtensionElement -{ - @state() - _mediaType?: MediaTypeResponseModel; - - private _workspaceContext?: typeof UMB_MEDIA_TYPE_WORKSPACE_CONTEXT.TYPE; - - constructor() { - super(); - - this.consumeContext(UMB_MEDIA_TYPE_WORKSPACE_CONTEXT, (_instance) => { - this._workspaceContext = _instance; - this._observeMediaType(); - }); - } - - private _observeMediaType() { - if (!this._workspaceContext) { - return; - } - - this.observe(this._workspaceContext.data, (mediaType) => { - this._mediaType = mediaType; - }); - } - - render() { - return html` List View view for ${this._mediaType?.alias}`; - } - - static styles = [ - UmbTextStyles, - css` - :host { - display: block; - margin: var(--uui-size-layout-1); - padding-bottom: var(--uui-size-layout-1); - } - - uui-box { - margin-top: var(--uui-size-layout-1); - } - `, - ]; -} - -export default UmbMediaTypeListViewWorkspaceViewEditElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-media-type-list-view-workspace-view': UmbMediaTypeListViewWorkspaceViewEditElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/details/media-type-permissions-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/details/media-type-permissions-workspace-view.element.ts deleted file mode 100644 index 5dc1a4eab3..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/details/media-type-permissions-workspace-view.element.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { UMB_MEDIA_TYPE_WORKSPACE_CONTEXT } from '../../media-type-workspace.context.js'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { css, html, nothing, customElement, state } from '@umbraco-cms/backoffice/external/lit'; -import { - UmbModalManagerContext, - UMB_MODAL_MANAGER_CONTEXT_TOKEN, - UMB_PROPERTY_EDITOR_UI_PICKER_MODAL, -} from '@umbraco-cms/backoffice/modal'; -import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { MediaTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; -import { UmbWorkspaceEditorViewExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; - -@customElement('umb-media-type-permissions-workspace-view') -export class UmbMediaTypePermissionsWorkspaceViewEditElement - extends UmbLitElement - implements UmbWorkspaceEditorViewExtensionElement -{ - @state() - _mediaType?: MediaTypeResponseModel; - - private _workspaceContext?: typeof UMB_MEDIA_TYPE_WORKSPACE_CONTEXT.TYPE; - - constructor() { - super(); - - this.consumeContext(UMB_MEDIA_TYPE_WORKSPACE_CONTEXT, (_instance) => { - this._workspaceContext = _instance; - this._observeMediaType(); - }); - } - - private _observeMediaType() { - if (!this._workspaceContext) { - return; - } - - this.observe(this._workspaceContext.data, (mediaType) => { - this._mediaType = mediaType; - }); - } - - render() { - return html`Permissions View for ${this._mediaType?.alias}`; - } - - static styles = [ - UmbTextStyles, - css` - :host { - display: block; - margin: var(--uui-size-layout-1); - padding-bottom: var(--uui-size-layout-1); - } - - uui-box { - margin-top: var(--uui-size-layout-1); - } - `, - ]; -} - -export default UmbMediaTypePermissionsWorkspaceViewEditElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-media-type-permissions-workspace-view': UmbMediaTypePermissionsWorkspaceViewEditElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/structure/media-type-workspace-view-structure.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/structure/media-type-workspace-view-structure.element.ts new file mode 100644 index 0000000000..46a2206a8b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/structure/media-type-workspace-view-structure.element.ts @@ -0,0 +1,117 @@ +import { UmbMediaTypeWorkspaceContext } from '../../media-type-workspace.context.js'; +import type { UmbMediaTypeInputElement } from '../../../components/media-type-input/media-type-input.element.js'; +import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import type { UUIToggleElement } from '@umbraco-cms/backoffice/external/uui'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; +import { UmbWorkspaceEditorViewExtensionElement } from '@umbraco-cms/backoffice/extension-registry'; + +@customElement('umb-media-type-workspace-view-structure') +export class UmbMediaTypeWorkspaceViewStructureElement + extends UmbLitElement + implements UmbWorkspaceEditorViewExtensionElement +{ + #workspaceContext?: UmbMediaTypeWorkspaceContext; + + @state() + private _allowedAsRoot?: boolean; + + @state() + private _allowedContentTypeIDs?: Array; + + constructor() { + super(); + + // TODO: Figure out if this is the best way to consume the context or if it can be strongly typed with an UmbContextToken + this.consumeContext(UMB_WORKSPACE_CONTEXT, (mediaTypeContext) => { + this.#workspaceContext = mediaTypeContext as UmbMediaTypeWorkspaceContext; + this._observeMediaType(); + }); + } + + private _observeMediaType() { + if (!this.#workspaceContext) return; + this.observe(this.#workspaceContext.allowedAsRoot, (allowedAsRoot) => (this._allowedAsRoot = allowedAsRoot)); + this.observe(this.#workspaceContext.allowedContentTypes, (allowedContentTypes) => { + const oldValue = this._allowedContentTypeIDs; + this._allowedContentTypeIDs = allowedContentTypes + ?.map((x) => x.id) + .filter((x) => x !== undefined) as Array; + this.requestUpdate('_allowedContentTypeIDs', oldValue); + }); + } + + render() { + return html` + + +
${this.localize.term('contentTypeEditor_allowAsRootDescription')}
+
+ { + this.#workspaceContext?.updateProperty('allowedAsRoot', (e.target as UUIToggleElement).checked); + }}> +
+
+ +
+ Allow content of the specified types to be created underneath content of this type. +
+
+ + + +
+
+
+ + +
Provides an overview of child content and hides it in the tree.
+
+
+
+ `; + } + + static styles = [ + UmbTextStyles, + css` + :host { + display: block; + margin: var(--uui-size-layout-1); + padding-bottom: var(--uui-size-layout-1); // To enforce some distance to the bottom of the scroll-container. + } + uui-box { + margin-top: var(--uui-size-layout-1); + } + uui-label, + umb-property-editor-ui-number { + display: block; + } + + // TODO: is this necessary? + uui-toggle { + display: flex; + } + `, + ]; +} + +export default UmbMediaTypeWorkspaceViewStructureElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-media-type-workspace-view-structure': UmbMediaTypeWorkspaceViewStructureElement; + } +} From 62727fce9b813b959abbca834eaa1a3201d52db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Thu, 16 Nov 2023 19:08:02 +1300 Subject: [PATCH 04/25] create action --- .../entity-actions/create.action.ts | 14 ---- .../entity-actions/create/create.action.ts | 26 +++++++ .../entity-actions/create/manifests.ts | 28 +++++++ .../entity-actions/create/modal/index.ts | 13 ++++ ...media-type-create-options-modal.element.ts | 76 +++++++++++++++++++ .../media-types/entity-actions/manifests.ts | 19 +---- .../entity-actions/reload.action.ts | 16 ---- 7 files changed, 146 insertions(+), 46 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create.action.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/create.action.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/modal/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/modal/media-type-create-options-modal.element.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/reload.action.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create.action.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create.action.ts deleted file mode 100644 index 01cb440514..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create.action.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { UmbMediaTypeRepository } from '../repository/media-type.repository.js'; -import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; - -export class UmbCreateMediaTypeEntityAction extends UmbEntityActionBase { - constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) { - super(host, repositoryAlias, unique); - } - - async execute() { - console.log(`execute for: ${this.unique}`); - alert('open create dialog'); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/create.action.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/create.action.ts new file mode 100644 index 0000000000..84d6ecf8f0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/create.action.ts @@ -0,0 +1,26 @@ +import { UmbMediaTypeDetailRepository } from '../../repository/detail/media-type-detail.repository.js'; +import { UMB_MEDIA_TYPE_CREATE_OPTIONS_MODAL } from './modal/index.js'; +import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UmbModalManagerContext, UMB_MODAL_MANAGER_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/modal'; + +export class UmbCreateMediaTypeEntityAction extends UmbEntityActionBase { + #modalManagerContext?: UmbModalManagerContext; + + constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) { + super(host, repositoryAlias, unique); + + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (instance) => { + this.#modalManagerContext = instance; + }); + } + + async execute() { + if (!this.#modalManagerContext) throw new Error('Modal manager context is not available'); + if (!this.repository) throw new Error('Repository is not available'); + + this.#modalManagerContext?.open(UMB_MEDIA_TYPE_CREATE_OPTIONS_MODAL, { + parentKey: this.unique, + }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts new file mode 100644 index 0000000000..cb4410d7e5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts @@ -0,0 +1,28 @@ +import { MEDIA_TYPE_ENTITY_TYPE, MEDIA_TYPE_FOLDER_ENTITY_TYPE, MEDIA_TYPE_ROOT_ENTITY_TYPE } from '../../index.js'; +import { MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS } from '../../repository/index.js'; +import { UmbCreateMediaTypeEntityAction } from './create.action.js'; +import { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +const entityActions: Array = [ + { + type: 'entityAction', + alias: 'Umb.EntityAction.MediaType.Create', + name: 'Create Media Type Entity Action', + weight: 1000, + api: UmbCreateMediaTypeEntityAction, + meta: { + icon: 'icon-add', + label: 'Create...', + repositoryAlias: MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, + entityTypes: [MEDIA_TYPE_ENTITY_TYPE, MEDIA_TYPE_ROOT_ENTITY_TYPE, MEDIA_TYPE_FOLDER_ENTITY_TYPE], + }, + }, + { + type: 'modal', + alias: 'Umb.Modal.MediaTypeCreateOptions', + name: 'Media Type Create Options Modal', + loader: () => import('./modal/media-type-create-options-modal.element.js'), + }, +]; + +export const manifests = [...entityActions]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/modal/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/modal/index.ts new file mode 100644 index 0000000000..dcc72118a7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/modal/index.ts @@ -0,0 +1,13 @@ +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; + +export interface UmbMediaTypeCreateOptionsModalData { + parentKey: string | null; +} + +export const UMB_MEDIA_TYPE_CREATE_OPTIONS_MODAL = new UmbModalToken( + 'Umb.Modal.MediaTypeCreateOptions', + { + type: 'sidebar', + size: 'small', + }, +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/modal/media-type-create-options-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/modal/media-type-create-options-modal.element.ts new file mode 100644 index 0000000000..b7bb4b9865 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/modal/media-type-create-options-modal.element.ts @@ -0,0 +1,76 @@ +import { MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS } from '../../../repository/index.js'; +import { UmbMediaTypeCreateOptionsModalData } from './index.js'; +import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { + UmbModalManagerContext, + UmbModalContext, + UMB_FOLDER_MODAL, + UMB_MODAL_MANAGER_CONTEXT_TOKEN, +} from '@umbraco-cms/backoffice/modal'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; + +@customElement('umb-media-type-create-options-modal') +export class UmbDataTypeCreateOptionsModalElement extends UmbLitElement { + @property({ attribute: false }) + modalContext?: UmbModalContext; + + @property({ type: Object }) + data?: UmbMediaTypeCreateOptionsModalData; + + #modalContext?: UmbModalManagerContext; + + constructor() { + super(); + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (instance) => { + this.#modalContext = instance; + }); + } + + #onClick(event: PointerEvent) { + event.stopPropagation(); + const folderModalHandler = this.#modalContext?.open(UMB_FOLDER_MODAL, { + repositoryAlias: MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, + }); + folderModalHandler?.onSubmit().then(() => this.modalContext?.submit()); + } + + // close the modal when navigating to data type + #onNavigate() { + this.modalContext?.submit(); + } + + #onCancel() { + this.modalContext?.reject(); + } + + render() { + return html` + + + + + + + + + + + Cancel + + `; + } + + static styles = [UmbTextStyles]; +} + +export default UmbDataTypeCreateOptionsModalElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-media-type-create-options-modal': UmbDataTypeCreateOptionsModalElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/manifests.ts index 0d68d6334b..f66e7b6d40 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/manifests.ts @@ -1,6 +1,6 @@ import { MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS } from '../index.js'; -import { UmbCreateMediaTypeEntityAction } from './create.action.js'; -import UmbReloadMediaTypeEntityAction from './reload.action.js'; +import { UmbCreateMediaTypeEntityAction } from './create/create.action.js'; +import { manifests as createManifests } from './create/manifests.js'; import { UmbDeleteEntityAction, UmbMoveEntityAction, UmbCopyEntityAction } from '@umbraco-cms/backoffice/entity-action'; import type { ManifestEntityAction } from '@umbraco-cms/backoffice/extension-registry'; @@ -60,19 +60,6 @@ const entityActions: Array = [ entityTypes: [entityType], }, }, - { - type: 'entityAction', - alias: 'Umb.EntityAction.MediaType.Reload', - name: 'Reload Media Type Entity Action', - weight: 100, - api: UmbReloadMediaTypeEntityAction, - meta: { - icon: 'icon-refresh', - label: 'Reload', - repositoryAlias, - entityTypes: [entityType], - }, - }, ]; -export const manifests = [...entityActions]; +export const manifests = [...entityActions, ...createManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/reload.action.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/reload.action.ts deleted file mode 100644 index 5fc349832a..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/reload.action.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { UmbMediaTypeRepository } from '../repository/media-type.repository.js'; -import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; -import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; - -export default class UmbReloadMediaTypeEntityAction extends UmbEntityActionBase { - static styles = [UmbTextStyles]; - - constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) { - super(host, repositoryAlias, unique); - } - - async execute() { - alert('refresh'); - } -} From 664837a59f5cbbace114a9c46ca2b9c5bd419271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Thu, 16 Nov 2023 19:11:40 +1300 Subject: [PATCH 05/25] create route --- .../workspace/media-type-workspace.element.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.element.ts index c5d3a1f6f9..6986325317 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.element.ts @@ -4,6 +4,7 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import type { UmbRoute } from '@umbraco-cms/backoffice/router'; +import { UmbWorkspaceIsNewRedirectController } from '@umbraco-cms/backoffice/workspace'; @customElement('umb-media-type-workspace') export class UmbMediaTypeWorkspaceElement extends UmbLitElement { @@ -12,6 +13,20 @@ export class UmbMediaTypeWorkspaceElement extends UmbLitElement { @state() _routes: UmbRoute[] = [ + { + path: 'create/:parentId', + component: import('./media-type-workspace-editor.element.js'), + setup: (_component, info) => { + const parentId = info.match.params.parentId === 'null' ? null : info.match.params.parentId; + this.#workspaceContext.create(parentId); + + new UmbWorkspaceIsNewRedirectController( + this, + this.#workspaceContext, + this.shadowRoot!.querySelector('umb-router-slot')!, + ); + }, + }, { path: 'edit/:id', component: () => this.#element, From 12e28f4c1e093cae56ac0c88bf0b25dbd426b441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Mon, 20 Nov 2023 15:30:10 +1300 Subject: [PATCH 06/25] correct scaffolding icon --- .../media-types/repository/detail/media-type.server.data.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type.server.data.ts index c54ad99532..4abebc0f57 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type.server.data.ts @@ -62,7 +62,7 @@ export class UmbMediaTypeServerDataSource name: '', alias: '', description: '', - icon: 'icon-media', + icon: 'icon-picture', allowedAsRoot: false, variesByCulture: false, variesBySegment: false, From dfe0f52965fa2a1809e6bd6c308351fb929b15c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Mon, 20 Nov 2023 22:20:13 +1300 Subject: [PATCH 07/25] fix merge --- .../entity-actions/create/manifests.ts | 10 +- .../detail/media-type-detail.repository.ts | 11 +- .../detail/media-type.server.data.ts | 147 ++++++++++++++++++ .../tree/media-type-tree.repository.ts | 4 +- .../media/media-types/workspace/manifests.ts | 4 +- .../workspace/media-type-workspace.context.ts | 16 +- 6 files changed, 166 insertions(+), 26 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type.server.data.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts index cb4410d7e5..f8af53e68f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts @@ -1,4 +1,8 @@ -import { MEDIA_TYPE_ENTITY_TYPE, MEDIA_TYPE_FOLDER_ENTITY_TYPE, MEDIA_TYPE_ROOT_ENTITY_TYPE } from '../../index.js'; +import { + UMB_MEDIA_TYPE_ENTITY_TYPE, + MEDIA_TYPE_FOLDER_ENTITY_TYPE, + UMB_MEDIA_TYPE_ROOT_ENTITY_TYPE, +} from '../../index.js'; import { MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS } from '../../repository/index.js'; import { UmbCreateMediaTypeEntityAction } from './create.action.js'; import { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; @@ -14,14 +18,14 @@ const entityActions: Array = [ icon: 'icon-add', label: 'Create...', repositoryAlias: MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, - entityTypes: [MEDIA_TYPE_ENTITY_TYPE, MEDIA_TYPE_ROOT_ENTITY_TYPE, MEDIA_TYPE_FOLDER_ENTITY_TYPE], + entityTypes: [UMB_MEDIA_TYPE_ENTITY_TYPE, UMB_MEDIA_TYPE_ROOT_ENTITY_TYPE, MEDIA_TYPE_FOLDER_ENTITY_TYPE], }, }, { type: 'modal', alias: 'Umb.Modal.MediaTypeCreateOptions', name: 'Media Type Create Options Modal', - loader: () => import('./modal/media-type-create-options-modal.element.js'), + js: () => import('./modal/media-type-create-options-modal.element.js'), }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts index 11f09fa0ed..987ad2957f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts @@ -76,7 +76,7 @@ export class UmbMediaTypeDetailRepository if (!id) throw new Error('Id is missing'); await this.#init; - const { data, error } = await this.#detailDataSource.get(id); + const { data, error } = await this.#detailDataSource.read(id); if (data) { this.#detailStore?.append(data); @@ -91,20 +91,13 @@ export class UmbMediaTypeDetailRepository return this.#detailStore!.byId(id); } - // TODO: we need to figure out where to put this - async requestAllowedChildTypesOf(id: string) { - if (!id) throw new Error('Id is missing'); - await this.#init; - return this.#detailDataSource.getAllowedChildrenOf(id); - } - // Could potentially be general methods: async create(mediaType: ItemType) { if (!mediaType || !mediaType.id) throw new Error('Media Type is missing'); await this.#init; - const { error } = await this.#detailDataSource.insert(mediaType); + const { error } = await this.#detailDataSource.create(mediaType); if (!error) { this.#detailStore?.append(mediaType); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type.server.data.ts new file mode 100644 index 0000000000..0e4d12fe2c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type.server.data.ts @@ -0,0 +1,147 @@ +import type { UmbDataSource } from '@umbraco-cms/backoffice/repository'; +import { + CreateMediaTypeRequestModel, + MediaTypeResource, + MediaTypeResponseModel, + UpdateMediaTypeRequestModel, +} from '@umbraco-cms/backoffice/backend-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; +import { UmbId } from '@umbraco-cms/backoffice/id'; + +/** + * A data source for the Media Type that fetches data from the server + * @export + * @class UmbMediaTypeServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbMediaTypeServerDataSource + implements UmbDataSource +{ + #host: UmbControllerHost; + + /** + * Creates an instance of UmbMediaServerDataSource. + * @param {UmbControllerHost} host + * @memberof UmbMediaServerDataSource + */ + constructor(host: UmbControllerHost) { + this.#host = host; + } + + /** + * Fetches a Media with the given id from the server + * @param {string} id + * @return {*} + * @memberof UmbMediaTypeServerDataSource + */ + async read(id: string) { + if (!id) { + throw new Error('Id is missing'); + } + + return tryExecuteAndNotify( + this.#host, + MediaTypeResource.getMediaTypeById({ + id: id, + }), + ); + } + + /** + * Creates a new Media scaffold + * @param {(string | null)} parentId + * @return {*} + * @memberof UmbMediaTypeServerDataSource + */ + async createScaffold(parentId: string | null) { + //, parentId: string | null + const data: MediaTypeResponseModel = { + id: UmbId.new(), + //parentId: parentId, + name: '', + alias: '', + description: '', + icon: 'icon-picture', + allowedAsRoot: false, + variesByCulture: false, + variesBySegment: false, + isElement: false, + allowedContentTypes: [], + compositions: [], + properties: [], + containers: [], + }; + + return { data }; + } + + /** + * Creates a new Media Type on the server + * @param {CreateMediaTypeRequestModel} mediaType + * @return {*} + * @memberof UmbMediaTypeServerDataSource + */ + async create(mediaType: CreateMediaTypeRequestModel) { + if (!mediaType) throw new Error('Media Type is missing'); + return tryExecuteAndNotify( + this.#host, + MediaTypeResource.postMediaType({ + requestBody: mediaType, + }), + ); + } + + /** + * Updates a Media Type on the server + * @param {string} id + * @param {Media} mediaType + * @return {*} + * @memberof UmbMediaTypeServerDataSource + */ + async update(id: string, mediaType: UpdateMediaTypeRequestModel) { + if (!id) throw new Error('Id is missing'); + + mediaType = { ...mediaType }; + + // TODO: Hack to remove some props that ruins the media-type post end-point. + (mediaType as any).id = undefined; + + return tryExecuteAndNotify(this.#host, MediaTypeResource.putMediaTypeById({ id, requestBody: mediaType })); + } + + /** + * Deletes a Template on the server + * @param {string} id + * @return {*} + * @memberof UmbMediaTypeServerDataSource + */ + async delete(id: string) { + if (!id) { + throw new Error('Id is missing'); + } + + // TODO: Hack the type to avoid type-error here: + return tryExecuteAndNotify(this.#host, MediaTypeResource.deleteMediaTypeById({ id })) as any; + } + + /** + * Get the allowed media types for a given parent id + * @param {string} id + * @return {*} + * @memberof UmbMediaTypeServerDataSource + */ + async getAllowedChildrenOf(id: string) { + if (!id) throw new Error('Id is missing'); + + return tryExecuteAndNotify( + this.#host, + fetch(`/umbraco/management/api/v1/media-type/allowed-children-of/${id}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }).then((res) => res.json()), + ); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.repository.ts index 8096edbcf6..68258e60f7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.repository.ts @@ -1,4 +1,4 @@ -import { MEDIA_TYPE_ROOT_ENTITY_TYPE } from '../index.js'; +import { UMB_MEDIA_TYPE_ROOT_ENTITY_TYPE } from '../index.js'; import { UmbMediaTypeTreeServerDataSource } from './media-type.tree.server.data-source.js'; import { UMB_MEDIA_TYPE_TREE_STORE_CONTEXT } from './media-type.tree.store.js'; import { UmbMediaTypeTreeItemModel, UmbMediaTypeTreeRootModel } from './types.js'; @@ -17,7 +17,7 @@ export class UmbMediaTypeTreeRepository async requestTreeRoot() { const data = { id: null, - type: MEDIA_TYPE_ROOT_ENTITY_TYPE, + type: UMB_MEDIA_TYPE_ROOT_ENTITY_TYPE, name: 'Media Types', icon: 'icon-folder', hasChildren: true, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/manifests.ts index f4533f6825..729234545a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/manifests.ts @@ -22,7 +22,7 @@ const workspaceEditorViews: Array = [ type: 'workspaceEditorView', alias: 'Umb.WorkspaceView.MediaType.Design', name: 'Media Type Workspace Design View', - loader: () => import('./views/design/media-type-workspace-view-edit.element.js'), + js: () => import('./views/design/media-type-workspace-view-edit.element.js'), weight: 1000, meta: { label: 'Design', @@ -40,7 +40,7 @@ const workspaceEditorViews: Array = [ type: 'workspaceEditorView', alias: 'Umb.WorkspaceView.MediaType.Structure', name: 'Media Type Workspace Structure View', - loader: () => import('./views/structure/media-type-workspace-view-structure.element.js'), + js: () => import('./views/structure/media-type-workspace-view-structure.element.js'), weight: 800, meta: { label: 'Structure', diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts index 253f4b18a2..650b0eb401 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts @@ -1,21 +1,17 @@ -import { UmbMediaTypeRepository } from '../repository/media-type.repository.js'; -import { UmbSaveableWorkspaceContextInterface, UmbEditableWorkspaceContextBase } from '@umbraco-cms/backoffice/workspace'; import { UmbMediaTypeDetailRepository } from '../repository/detail/media-type-detail.repository.js'; +import { + UmbSaveableWorkspaceContextInterface, + UmbEditableWorkspaceContextBase, +} from '@umbraco-cms/backoffice/workspace'; import { UmbContentTypePropertyStructureManager } from '@umbraco-cms/backoffice/content-type'; -import { UmbWorkspaceContext, UmbSaveableWorkspaceContextInterface } from '@umbraco-cms/backoffice/workspace'; -import type { - ContentTypeCompositionModel, - ContentTypeSortModel, - MediaTypeResponseModel, -} from '@umbraco-cms/backoffice/backend-api'; +import { type MediaTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api'; type EntityType = MediaTypeResponseModel; export class UmbMediaTypeWorkspaceContext - extends UmbEditableWorkspaceContextBase - extends UmbWorkspaceContext + extends UmbEditableWorkspaceContextBase implements UmbSaveableWorkspaceContextInterface { // Draft is located in structure manager From e42818f8ea3a13c8dfc25136caebf9de77c2671f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Mon, 20 Nov 2023 22:21:09 +1300 Subject: [PATCH 08/25] reanme --- .../media/media-types/entity-actions/create/manifests.ts | 4 ++-- .../src/packages/media/media-types/index.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts index f8af53e68f..133fee928d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts @@ -1,6 +1,6 @@ import { UMB_MEDIA_TYPE_ENTITY_TYPE, - MEDIA_TYPE_FOLDER_ENTITY_TYPE, + UMB_MEDIA_TYPE_FOLDER_ENTITY_TYPE, UMB_MEDIA_TYPE_ROOT_ENTITY_TYPE, } from '../../index.js'; import { MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS } from '../../repository/index.js'; @@ -18,7 +18,7 @@ const entityActions: Array = [ icon: 'icon-add', label: 'Create...', repositoryAlias: MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, - entityTypes: [UMB_MEDIA_TYPE_ENTITY_TYPE, UMB_MEDIA_TYPE_ROOT_ENTITY_TYPE, MEDIA_TYPE_FOLDER_ENTITY_TYPE], + entityTypes: [UMB_MEDIA_TYPE_ENTITY_TYPE, UMB_MEDIA_TYPE_ROOT_ENTITY_TYPE, UMB_MEDIA_TYPE_FOLDER_ENTITY_TYPE], }, }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/index.ts index 97f2c76252..44a4d20d5a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/index.ts @@ -2,6 +2,6 @@ import './components/index.js'; export * from './repository/index.js'; -export const MEDIA_TYPE_ROOT_ENTITY_TYPE = 'media-type-root'; -export const MEDIA_TYPE_ENTITY_TYPE = 'media-type'; -export const MEDIA_TYPE_FOLDER_ENTITY_TYPE = 'media-type-folder'; +export const UMB_MEDIA_TYPE_ROOT_ENTITY_TYPE = 'media-type-root'; +export const UMB_MEDIA_TYPE_ENTITY_TYPE = 'media-type'; +export const UMB_MEDIA_TYPE_FOLDER_ENTITY_TYPE = 'media-type-folder'; From 047062528637b79aec4901105e58a1113ae90bba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Mon, 20 Nov 2023 22:22:07 +1300 Subject: [PATCH 09/25] lint happy --- .../media/media-types/menu-item/manifests.ts | 4 ++-- .../media/media-types/repository/detail/index.ts | 5 ++++- .../media-types/repository/detail/manifests.ts | 8 ++++---- .../media/media-types/repository/item/index.ts | 5 ++++- .../media/media-types/repository/item/manifests.ts | 8 ++++---- .../packages/media/media-types/tree/manifests.ts | 14 +++++++------- 6 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu-item/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu-item/manifests.ts index 0213b79f29..4e0f69b5f5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu-item/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu-item/manifests.ts @@ -1,4 +1,4 @@ -import { MEDIA_TYPE_TREE_ALIAS } from '../tree/manifests.js'; +import { UMB_MEDIA_TYPE_TREE_ALIAS } from '../tree/manifests.js'; import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; const menuItem: ManifestTypes = { @@ -10,7 +10,7 @@ const menuItem: ManifestTypes = { meta: { label: 'Media Types', icon: 'icon-folder', - treeAlias: MEDIA_TYPE_TREE_ALIAS, + treeAlias: UMB_MEDIA_TYPE_TREE_ALIAS, menus: ['Umb.Menu.Settings'], }, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/index.ts index 50410ebfa6..16fd2692c6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/index.ts @@ -1,3 +1,6 @@ export { UmbMediaTypeDetailRepository } from './media-type-detail.repository.js'; -export { MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, MEDIA_TYPE_DETAIL_STORE_ALIAS } from './manifests.js'; +export { + UMB_MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS as MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, + UMB_MEDIA_TYPE_DETAIL_STORE_ALIAS as MEDIA_TYPE_DETAIL_STORE_ALIAS, +} from './manifests.js'; export { UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT } from './media-type-detail.store.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/manifests.ts index 386a16c384..614536af72 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/manifests.ts @@ -2,19 +2,19 @@ import { UmbMediaTypeDetailRepository } from './media-type-detail.repository.js' import { UmbMediaTypeDetailStore } from './media-type-detail.store.js'; import { ManifestRepository, ManifestStore } from '@umbraco-cms/backoffice/extension-registry'; -export const MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS = 'Umb.Repository.MediaType.Detail'; -export const MEDIA_TYPE_DETAIL_STORE_ALIAS = 'Umb.Store.MediaType.Detail'; +export const UMB_MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS = 'Umb.Repository.MediaType.Detail'; +export const UMB_MEDIA_TYPE_DETAIL_STORE_ALIAS = 'Umb.Store.MediaType.Detail'; const detailRepository: ManifestRepository = { type: 'repository', - alias: MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, + alias: UMB_MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, name: 'Media Types Repository', api: UmbMediaTypeDetailRepository, }; const detailStore: ManifestStore = { type: 'store', - alias: MEDIA_TYPE_DETAIL_STORE_ALIAS, + alias: UMB_MEDIA_TYPE_DETAIL_STORE_ALIAS, name: 'Media Type Store', api: UmbMediaTypeDetailStore, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/index.ts index eb23683fec..2277e6c66b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/index.ts @@ -1,3 +1,6 @@ export { UmbMediaTypeItemRepository } from './media-type-item.repository.js'; -export { MEDIA_TYPE_ITEM_REPOSITORY_ALIAS, MEDIA_TYPE_ITEM_STORE_ALIAS } from './manifests.js'; +export { + UMB_MEDIA_TYPE_ITEM_REPOSITORY_ALIAS as MEDIA_TYPE_ITEM_REPOSITORY_ALIAS, + UMB_MEDIA_TYPE_ITEM_STORE_ALIAS as MEDIA_TYPE_ITEM_STORE_ALIAS, +} from './manifests.js'; export * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/manifests.ts index 0793bcbf34..f267e9e3db 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/manifests.ts @@ -2,19 +2,19 @@ import { UmbMediaTypeItemRepository } from './media-type-item.repository.js'; import { UmbMediaTypeItemStore } from './media-type-item.store.js'; import { ManifestItemStore, ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; -export const MEDIA_TYPE_ITEM_REPOSITORY_ALIAS = 'Umb.Repository.MediaType.Item'; -export const MEDIA_TYPE_ITEM_STORE_ALIAS = 'Umb.Store.MediaType.Item'; +export const UMB_MEDIA_TYPE_ITEM_REPOSITORY_ALIAS = 'Umb.Repository.MediaType.Item'; +export const UMB_MEDIA_TYPE_ITEM_STORE_ALIAS = 'Umb.Store.MediaType.Item'; const itemRepository: ManifestRepository = { type: 'repository', - alias: MEDIA_TYPE_ITEM_REPOSITORY_ALIAS, + alias: UMB_MEDIA_TYPE_ITEM_REPOSITORY_ALIAS, name: 'Media Type Item Repository', api: UmbMediaTypeItemRepository, }; const itemStore: ManifestItemStore = { type: 'itemStore', - alias: MEDIA_TYPE_ITEM_STORE_ALIAS, + alias: UMB_MEDIA_TYPE_ITEM_STORE_ALIAS, name: 'Media Type Item Store', api: UmbMediaTypeItemStore, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/manifests.ts index bbde15f412..fba8faea74 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/manifests.ts @@ -7,30 +7,30 @@ import type { ManifestTreeStore, } from '@umbraco-cms/backoffice/extension-registry'; -export const MEDIA_TYPE_TREE_REPOSITORY_ALIAS = 'Umb.Repository.MediaType.Tree'; -export const MEDIA_TYPE_TREE_STORE_ALIAS = 'Umb.Store.MediaType.Tree'; -export const MEDIA_TYPE_TREE_ALIAS = 'Umb.Tree.MediaType'; +export const UMB_MEDIA_TYPE_TREE_REPOSITORY_ALIAS = 'Umb.Repository.MediaType.Tree'; +export const UMB_MEDIA_TYPE_TREE_STORE_ALIAS = 'Umb.Store.MediaType.Tree'; +export const UMB_MEDIA_TYPE_TREE_ALIAS = 'Umb.Tree.MediaType'; const treeRepository: ManifestRepository = { type: 'repository', - alias: MEDIA_TYPE_TREE_REPOSITORY_ALIAS, + alias: UMB_MEDIA_TYPE_TREE_REPOSITORY_ALIAS, name: 'Media Type Tree Repository', api: UmbMediaTypeTreeRepository, }; const treeStore: ManifestTreeStore = { type: 'treeStore', - alias: MEDIA_TYPE_TREE_STORE_ALIAS, + alias: UMB_MEDIA_TYPE_TREE_STORE_ALIAS, name: 'Media Type Tree Store', api: UmbMediaTypeTreeStore, }; const tree: ManifestTree = { type: 'tree', - alias: MEDIA_TYPE_TREE_ALIAS, + alias: UMB_MEDIA_TYPE_TREE_ALIAS, name: 'Media Type Tree', meta: { - repositoryAlias: MEDIA_TYPE_TREE_REPOSITORY_ALIAS, + repositoryAlias: UMB_MEDIA_TYPE_TREE_REPOSITORY_ALIAS, }, }; From 70c768175e03545df140f9df5067cc8715b2fb16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Mon, 20 Nov 2023 22:44:34 +1300 Subject: [PATCH 10/25] fix exports and types and naming --- .../src/packages/media/media-types/entity.ts | 3 +++ .../src/packages/media/media-types/index.ts | 15 +++++++++++---- .../media/media-types/repository/detail/index.ts | 5 +---- .../detail/media-type-detail.repository.ts | 4 ++-- ...ts => media-type-detail.server.data-source.ts} | 0 .../media/media-types/repository/detail/types.ts | 4 ++++ .../media/media-types/repository/item/index.ts | 8 +++----- .../src/packages/media/media-types/tree/index.ts | 10 ++++++++++ .../packages/media/media-types/tree/manifests.ts | 2 +- .../tree/media-type-tree.repository.ts | 4 ++-- ...e.ts => media-type-tree.server.data-source.ts} | 0 ...ype.tree.store.ts => media-type-tree.store.ts} | 0 .../workspace/media-type-workspace.context.ts | 5 +++-- 13 files changed, 40 insertions(+), 20 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity.ts rename src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/{media-type.server.data.ts => media-type-detail.server.data-source.ts} (100%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/index.ts rename src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/{media-type.tree.server.data-source.ts => media-type-tree.server.data-source.ts} (100%) rename src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/{media-type.tree.store.ts => media-type-tree.store.ts} (100%) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity.ts new file mode 100644 index 0000000000..fdae21ac4a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity.ts @@ -0,0 +1,3 @@ +export const UMB_MEDIA_TYPE_ROOT_ENTITY_TYPE = 'media-type-root'; +export const UMB_MEDIA_TYPE_ENTITY_TYPE = 'media-type'; +export const UMB_MEDIA_TYPE_FOLDER_ENTITY_TYPE = 'media-type-folder'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/index.ts index 44a4d20d5a..8cf991d68a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/index.ts @@ -1,7 +1,14 @@ import './components/index.js'; -export * from './repository/index.js'; +export { + UmbMediaTypeItemRepository, + UMB_MEDIA_TYPE_ITEM_STORE_ALIAS, + UMB_MEDIA_TYPE_DETAIL_STORE_ALIAS, + UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT, +} from './repository/index.js'; -export const UMB_MEDIA_TYPE_ROOT_ENTITY_TYPE = 'media-type-root'; -export const UMB_MEDIA_TYPE_ENTITY_TYPE = 'media-type'; -export const UMB_MEDIA_TYPE_FOLDER_ENTITY_TYPE = 'media-type-folder'; +export { + UMB_MEDIA_TYPE_ROOT_ENTITY_TYPE, + UMB_MEDIA_TYPE_ENTITY_TYPE, + UMB_MEDIA_TYPE_FOLDER_ENTITY_TYPE, +} from './entity.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/index.ts index 16fd2692c6..6b844c13ad 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/index.ts @@ -1,6 +1,3 @@ export { UmbMediaTypeDetailRepository } from './media-type-detail.repository.js'; -export { - UMB_MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS as MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, - UMB_MEDIA_TYPE_DETAIL_STORE_ALIAS as MEDIA_TYPE_DETAIL_STORE_ALIAS, -} from './manifests.js'; +export { UMB_MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, UMB_MEDIA_TYPE_DETAIL_STORE_ALIAS } from './manifests.js'; export { UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT } from './media-type-detail.store.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts index 987ad2957f..8ca70c649f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts @@ -1,6 +1,6 @@ -import { UMB_MEDIA_TYPE_TREE_STORE_CONTEXT, UmbMediaTypeTreeStore } from '../../tree/media-type.tree.store.js'; +import { UMB_MEDIA_TYPE_TREE_STORE_CONTEXT, UmbMediaTypeTreeStore } from '../../tree/media-type-tree.store.js'; import { UMB_MEDIA_TYPE_ITEM_STORE_CONTEXT, UmbMediaTypeItemStore } from '../item/media-type-item.store.js'; -import { UmbMediaTypeServerDataSource } from './media-type.server.data.js'; +import { UmbMediaTypeServerDataSource } from './media-type-detail.server.data-source.js'; import { UmbMediaTypeDetailStore, UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT } from './media-type-detail.store.js'; import { type UmbDetailRepository } from '@umbraco-cms/backoffice/repository'; import { UmbBaseController, type UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.server.data-source.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type.server.data.ts rename to src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.server.data-source.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/types.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/types.ts new file mode 100644 index 0000000000..e461548b00 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/types.ts @@ -0,0 +1,4 @@ +import { UMB_MEDIA_TYPE_ENTITY_TYPE } from '../../entity.js'; +import { MediaTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; + +export type UmbMediaTypeDetailModel = MediaTypeResponseModel & { entityType: typeof UMB_MEDIA_TYPE_ENTITY_TYPE }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/index.ts index 2277e6c66b..48354e1b73 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/index.ts @@ -1,6 +1,4 @@ export { UmbMediaTypeItemRepository } from './media-type-item.repository.js'; -export { - UMB_MEDIA_TYPE_ITEM_REPOSITORY_ALIAS as MEDIA_TYPE_ITEM_REPOSITORY_ALIAS, - UMB_MEDIA_TYPE_ITEM_STORE_ALIAS as MEDIA_TYPE_ITEM_STORE_ALIAS, -} from './manifests.js'; -export * from './types.js'; +export { UMB_MEDIA_TYPE_ITEM_REPOSITORY_ALIAS, UMB_MEDIA_TYPE_ITEM_STORE_ALIAS } from './manifests.js'; +export { UMB_MEDIA_TYPE_ITEM_STORE_CONTEXT } from './media-type-item.store.js'; +export type { UmbMediaTypeItemModel } from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/index.ts new file mode 100644 index 0000000000..c1e6582874 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/index.ts @@ -0,0 +1,10 @@ +export { + UMB_MEDIA_TYPE_TREE_ALIAS, + UMB_MEDIA_TYPE_TREE_STORE_ALIAS, + UMB_MEDIA_TYPE_TREE_REPOSITORY_ALIAS, +} from './manifests.js'; + +export { UmbMediaTypeTreeRepository } from './media-type-tree.repository.js'; +export { UMB_MEDIA_TYPE_TREE_STORE_CONTEXT } from './media-type-tree.store.js'; + +export type { UmbMediaTypeTreeItemModel, UmbMediaTypeTreeRootModel } from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/manifests.ts index fba8faea74..03cbfddbcf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/manifests.ts @@ -1,5 +1,5 @@ import { UmbMediaTypeTreeRepository } from './media-type-tree.repository.js'; -import { UmbMediaTypeTreeStore } from './media-type.tree.store.js'; +import { UmbMediaTypeTreeStore } from './media-type-tree.store.js'; import type { ManifestRepository, ManifestTree, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.repository.ts index 68258e60f7..125d2ef283 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.repository.ts @@ -1,6 +1,6 @@ import { UMB_MEDIA_TYPE_ROOT_ENTITY_TYPE } from '../index.js'; -import { UmbMediaTypeTreeServerDataSource } from './media-type.tree.server.data-source.js'; -import { UMB_MEDIA_TYPE_TREE_STORE_CONTEXT } from './media-type.tree.store.js'; +import { UmbMediaTypeTreeServerDataSource } from './media-type-tree.server.data-source.js'; +import { UMB_MEDIA_TYPE_TREE_STORE_CONTEXT } from './media-type-tree.store.js'; import { UmbMediaTypeTreeItemModel, UmbMediaTypeTreeRootModel } from './types.js'; import { UmbTreeRepositoryBase } from '@umbraco-cms/backoffice/tree'; import { type UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type.tree.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.server.data-source.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type.tree.server.data-source.ts rename to src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.server.data-source.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type.tree.store.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.store.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type.tree.store.ts rename to src/Umbraco.Web.UI.Client/src/packages/media/media-types/tree/media-type-tree.store.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts index 650b0eb401..1a7272354c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts @@ -1,4 +1,5 @@ import { UmbMediaTypeDetailRepository } from '../repository/detail/media-type-detail.repository.js'; +import { UMB_MEDIA_TYPE_ENTITY_TYPE } from '../index.js'; import { UmbSaveableWorkspaceContextInterface, UmbEditableWorkspaceContextBase, @@ -65,7 +66,7 @@ export class UmbMediaTypeWorkspaceContext } getEntityType() { - return 'media-type'; + return UMB_MEDIA_TYPE_ENTITY_TYPE; } updateProperty(propertyName: PropertyName, value: EntityType[PropertyName]) { @@ -120,5 +121,5 @@ export const UMB_MEDIA_TYPE_WORKSPACE_CONTEXT = new UmbContextToken< UmbMediaTypeWorkspaceContext >( 'UmbWorkspaceContext', - (context): context is UmbMediaTypeWorkspaceContext => context.getEntityType?.() === 'media-type', + (context): context is UmbMediaTypeWorkspaceContext => context.getEntityType?.() === UMB_MEDIA_TYPE_ENTITY_TYPE, ); From 55ebdca0d1d2789bf918edda976f66a57a000405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Mon, 20 Nov 2023 22:49:20 +1300 Subject: [PATCH 11/25] fix imports --- .../media-type-input.context.ts | 4 ++-- .../entity-actions/create/manifests.ts | 4 ++-- ...media-type-create-options-modal.element.ts | 4 ++-- .../media-types/entity-actions/manifests.ts | 22 +++++++++---------- .../item/media-type-item.repository.ts | 2 +- ... => media-type-item.server.data-source.ts} | 0 6 files changed, 17 insertions(+), 19 deletions(-) rename src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/{media-type-item.server.data.ts => media-type-item.server.data-source.ts} (100%) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/media-type-input/media-type-input.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/media-type-input/media-type-input.context.ts index 2642d93198..473677c56a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/media-type-input/media-type-input.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/media-type-input/media-type-input.context.ts @@ -1,4 +1,4 @@ -import { MEDIA_TYPE_ITEM_REPOSITORY_ALIAS } from '../../repository/index.js'; +import { UMB_MEDIA_TYPE_ITEM_REPOSITORY_ALIAS } from '../../repository/index.js'; import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UMB_MEDIA_TYPE_PICKER_MODAL } from '@umbraco-cms/backoffice/modal'; @@ -6,6 +6,6 @@ import { MediaTypeItemResponseModel } from '@umbraco-cms/backoffice/backend-api' export class UmbMediaTypePickerContext extends UmbPickerInputContext { constructor(host: UmbControllerHostElement) { - super(host, MEDIA_TYPE_ITEM_REPOSITORY_ALIAS, UMB_MEDIA_TYPE_PICKER_MODAL); + super(host, UMB_MEDIA_TYPE_ITEM_REPOSITORY_ALIAS, UMB_MEDIA_TYPE_PICKER_MODAL); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts index 133fee928d..e1195739df 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/manifests.ts @@ -3,7 +3,7 @@ import { UMB_MEDIA_TYPE_FOLDER_ENTITY_TYPE, UMB_MEDIA_TYPE_ROOT_ENTITY_TYPE, } from '../../index.js'; -import { MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS } from '../../repository/index.js'; +import { UMB_MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS } from '../../repository/index.js'; import { UmbCreateMediaTypeEntityAction } from './create.action.js'; import { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; @@ -17,7 +17,7 @@ const entityActions: Array = [ meta: { icon: 'icon-add', label: 'Create...', - repositoryAlias: MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, + repositoryAlias: UMB_MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, entityTypes: [UMB_MEDIA_TYPE_ENTITY_TYPE, UMB_MEDIA_TYPE_ROOT_ENTITY_TYPE, UMB_MEDIA_TYPE_FOLDER_ENTITY_TYPE], }, }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/modal/media-type-create-options-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/modal/media-type-create-options-modal.element.ts index b7bb4b9865..2f7a4cf080 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/modal/media-type-create-options-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/create/modal/media-type-create-options-modal.element.ts @@ -1,4 +1,4 @@ -import { MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS } from '../../../repository/index.js'; +import { UMB_MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS } from '../../../repository/index.js'; import { UmbMediaTypeCreateOptionsModalData } from './index.js'; import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; @@ -30,7 +30,7 @@ export class UmbDataTypeCreateOptionsModalElement extends UmbLitElement { #onClick(event: PointerEvent) { event.stopPropagation(); const folderModalHandler = this.#modalContext?.open(UMB_FOLDER_MODAL, { - repositoryAlias: MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, + repositoryAlias: UMB_MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, }); folderModalHandler?.onSubmit().then(() => this.modalContext?.submit()); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/manifests.ts index f66e7b6d40..9916090b2e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/entity-actions/manifests.ts @@ -1,12 +1,10 @@ -import { MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS } from '../index.js'; +import { UMB_MEDIA_TYPE_ENTITY_TYPE } from '../entity.js'; +import { UMB_MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS } from '../repository/index.js'; import { UmbCreateMediaTypeEntityAction } from './create/create.action.js'; import { manifests as createManifests } from './create/manifests.js'; import { UmbDeleteEntityAction, UmbMoveEntityAction, UmbCopyEntityAction } from '@umbraco-cms/backoffice/entity-action'; import type { ManifestEntityAction } from '@umbraco-cms/backoffice/extension-registry'; -const entityType = 'media-type'; -const repositoryAlias = MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS; - const entityActions: Array = [ { type: 'entityAction', @@ -17,8 +15,8 @@ const entityActions: Array = [ meta: { icon: 'icon-add', label: 'Create', - repositoryAlias, - entityTypes: [entityType], + repositoryAlias: UMB_MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, + entityTypes: [UMB_MEDIA_TYPE_ENTITY_TYPE], }, }, { @@ -30,8 +28,8 @@ const entityActions: Array = [ meta: { icon: 'icon-enter', label: 'Move', - repositoryAlias, - entityTypes: [entityType], + repositoryAlias: UMB_MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, + entityTypes: [UMB_MEDIA_TYPE_ENTITY_TYPE], }, }, { @@ -43,8 +41,8 @@ const entityActions: Array = [ meta: { icon: 'icon-documents', label: 'Copy', - repositoryAlias, - entityTypes: [entityType], + repositoryAlias: UMB_MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, + entityTypes: [UMB_MEDIA_TYPE_ENTITY_TYPE], }, }, { @@ -56,8 +54,8 @@ const entityActions: Array = [ meta: { icon: 'icon-trash', label: 'Delete', - repositoryAlias, - entityTypes: [entityType], + repositoryAlias: UMB_MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS, + entityTypes: [UMB_MEDIA_TYPE_ENTITY_TYPE], }, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.repository.ts index 06fe6fc814..8eec8ef031 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.repository.ts @@ -1,5 +1,5 @@ import { UmbMediaTypeItemModel } from './types.js'; -import { UmbMediaTypeItemServerDataSource } from './media-type-item.server.data.js'; +import { UmbMediaTypeItemServerDataSource } from './media-type-item.server.data-source.js'; import { UMB_MEDIA_TYPE_ITEM_STORE_CONTEXT } from './media-type-item.store.js'; import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbItemRepositoryBase } from '@umbraco-cms/backoffice/repository'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.server.data-source.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.server.data.ts rename to src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/item/media-type-item.server.data-source.ts From 695b2caeceba0710bc376083790ea7bcca900daa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Mon, 20 Nov 2023 22:53:46 +1300 Subject: [PATCH 12/25] update detail store --- .../repository/detail/media-type-detail.repository.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts index 8ca70c649f..49ae8eb686 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts @@ -102,7 +102,10 @@ export class UmbMediaTypeDetailRepository if (!error) { this.#detailStore?.append(mediaType); const treeItem = createTreeItem(mediaType); - this.#treeStore?.appendItems([treeItem]); + this.#treeStore?.append(treeItem); + + const notification = { data: { message: `Media Type created` } }; + this.#notificationContext?.peek('positive', notification); } return { error }; @@ -120,9 +123,9 @@ export class UmbMediaTypeDetailRepository // TODO: we currently don't use the detail store for anything. // Consider to look up the data before fetching from the server // Consider notify a workspace if a template is updated in the store while someone is editing it. - this.#detailStore?.append(item); + this.#detailStore?.updateItem(id, item); this.#treeStore?.updateItem(id, item); - // TODO: would be nice to align the stores on methods/methodNames. + this.#itemStore?.updateItem(id, item); const notification = { data: { message: `Media Type saved` } }; this.#notificationContext?.peek('positive', notification); From d596da66eb950643cf39edd7b1f411a9a1badf9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Mon, 20 Nov 2023 23:00:16 +1300 Subject: [PATCH 13/25] add notification --- .../repository/detail/media-type-detail.repository.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts index 49ae8eb686..b01d10316f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/detail/media-type-detail.repository.ts @@ -142,9 +142,6 @@ export class UmbMediaTypeDetailRepository const { error } = await this.#detailDataSource.delete(id); if (!error) { - const notification = { data: { message: `Media Type deleted` } }; - this.#notificationContext?.peek('positive', notification); - // TODO: we currently don't use the detail store for anything. // Consider to look up the data before fetching from the server. // Consider notify a workspace if a template is deleted from the store while someone is editing it. @@ -152,6 +149,9 @@ export class UmbMediaTypeDetailRepository this.#detailStore?.removeItem(id); this.#treeStore?.removeItem(id); this.#itemStore?.removeItem(id); + + const notification = { data: { message: `Media Type deleted` } }; + this.#notificationContext?.peek('positive', notification); } return { error }; From 87960cdd5b4be71d6d3796897fb6305309a2b0f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 21 Nov 2023 11:02:00 +0100 Subject: [PATCH 14/25] make a context alias and api alias --- .../context-api/consume/context-consumer.ts | 23 ++++++++---- .../consume/context-request.event.ts | 8 ++--- .../context-api/provide/context-provider.ts | 13 ++++--- .../libs/context-api/token/context-token.ts | 35 +++++++++++++------ 4 files changed, 53 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts index 777032acb1..4dff7b2a13 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.ts @@ -22,29 +22,32 @@ export class UmbContextConsumer; /** * Creates an instance of UmbContextConsumer. * @param {EventTarget} hostElement - * @param {string} contextAlias + * @param {string} contextIdentifier * @param {UmbContextCallback} callback * @memberof UmbContextConsumer */ constructor( protected hostElement: EventTarget, - contextAlias: string | UmbContextToken, + contextIdentifier: string | UmbContextToken, callback?: UmbContextCallback, ) { - this.#contextAlias = contextAlias.toString(); + const idSplit = contextIdentifier.toString().split('#'); + this.#contextAlias = idSplit[0]; + this.#apiAlias = idSplit[1] ?? 'default'; this.#callback = callback; - this.#discriminator = (contextAlias as UmbContextToken).getDiscriminator?.(); + this.#discriminator = (contextIdentifier as UmbContextToken).getDiscriminator?.(); } protected _onResponse = (instance: BaseType): boolean => { if (this.#instance === instance) { - return false; + return true; } if (this.#discriminator) { // Notice if discriminator returns false, we do not want to setInstance. @@ -68,6 +71,11 @@ export class UmbContextConsumer = (instance: T) => void; * @interface UmbContextRequestEvent */ export interface UmbContextRequestEvent extends Event { - readonly contextAlias: string | UmbContextToken; + readonly contextAlias: string; + readonly apiAlias: string; readonly callback: (context: ResultType) => boolean; } @@ -25,7 +24,8 @@ export class UmbContextRequestEventImplementation implements UmbContextRequestEvent { public constructor( - public readonly contextAlias: string | UmbContextToken, + public readonly contextAlias: string, + public readonly apiAlias: string, public readonly callback: (context: ResultType) => boolean, ) { super(UMB_CONTENT_REQUEST_EVENT_TYPE, { bubbles: true, composed: true, cancelable: true }); diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.ts index 74a9f62e5f..4cd04706de 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.ts @@ -17,6 +17,7 @@ export class UmbContextProvider, + contextIdentifier: string | UmbContextToken, instance: ResultType, ) { this.hostElement = hostElement; - this._contextAlias = contextAlias.toString(); + + const idSplit = contextIdentifier.toString().split('#'); + this._contextAlias = idSplit[0]; + this._apiAlias = idSplit[1] ?? 'default'; this.#instance = instance; } @@ -56,7 +60,8 @@ export class UmbContextProvider = ( + instance: BaseType, +) => instance is DiscriminatorResult; -export type UmbContextDiscriminator = (instance: BaseType) => instance is DiscriminatorResult; - -export class UmbContextToken< -BaseType = unknown, -ResultType extends BaseType = BaseType> { - +/** + * @export + * @class UmbContextToken + * @template BaseType - A generic type of the API before decimated. + * @template ResultType - A concrete type of the API after decimation, use this when you apply a discriminator method. Note this is optional and defaults to the BaseType. + */ +export class UmbContextToken { #discriminator: UmbContextDiscriminator | undefined; /** * Get the type of the token @@ -18,12 +22,23 @@ ResultType extends BaseType = BaseType> { readonly TYPE: ResultType = undefined as never; /** - * @param alias Unique identifier for the token + * @param contextAlias Unique identifier for the context + * @param apiAlias Unique identifier for the api + * @param discriminator A discriminator that will be used to discriminate the API — testing if the API lives up to a certain requirement. If the API does not meet the requirement then the consumer will not receive this API. */ - constructor(protected alias: string, discriminator?: UmbContextDiscriminator) { + constructor( + protected contextAlias: string, + protected apiAlias: string = 'default', + discriminator?: UmbContextDiscriminator, + ) { this.#discriminator = discriminator; } + /** + * Get the discriminator method for the token + * + * @returns the discriminator method + */ getDiscriminator(): UmbContextDiscriminator | undefined { return this.#discriminator; } @@ -35,8 +50,6 @@ ResultType extends BaseType = BaseType> { * @returns the unique alias of the token */ toString(): string { - return this.alias; + return this.contextAlias + '#' + this.apiAlias; } - - } From e7dd75978f6cfe019b5eb2cc66884ae4d21afb93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 21 Nov 2023 11:02:04 +0100 Subject: [PATCH 15/25] implement --- .../data-type-variant-context.token.ts | 14 +++++++++----- .../workspace/data-type-workspace.context.ts | 1 + .../nameable-variant-context.token.ts | 15 +++++++++------ .../variant-context/variant-context.token.ts | 6 +++--- .../variant-workspace-context.token.ts | 6 +++++- .../workspace/dictionary-workspace.context.ts | 6 +++++- .../workspace/document-type-workspace.context.ts | 1 + .../document-variant-context.token.ts | 1 + .../workspace/document-workspace.context.ts | 2 +- .../workspace/media-type-workspace.context.ts | 6 +++++- .../media/workspace/media-workspace.context.ts | 6 +++++- .../workspace/member-group-workspace.context.ts | 1 + .../workspace/member-type-workspace.context.ts | 13 ++++++++++--- .../members/workspace/member-workspace.context.ts | 1 + .../language/language-workspace.context.ts | 14 ++++++++++---- .../workspace/relation-type-workspace.context.ts | 15 ++++++++++----- .../workspace/partial-view-workspace.context.ts | 1 + .../workspace/stylesheet-workspace.context.ts | 1 + .../workspace/template-workspace.context.ts | 6 +++++- .../workspace/user-group-workspace.context.ts | 6 +++++- .../user/user/workspace/user-workspace.context.ts | 1 + 21 files changed, 90 insertions(+), 33 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/variant-context/data-type-variant-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/variant-context/data-type-variant-context.token.ts index 2bd6edee08..b30be8a749 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/variant-context/data-type-variant-context.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/variant-context/data-type-variant-context.token.ts @@ -1,8 +1,12 @@ -import type { UmbDataTypeVariantContext } from "./data-type-variant-context.js"; -import { UmbVariantContext } from "@umbraco-cms/backoffice/workspace"; -import { UmbContextToken } from "@umbraco-cms/backoffice/context-api"; +import type { UmbDataTypeVariantContext } from './data-type-variant-context.js'; +import { UmbVariantContext } from '@umbraco-cms/backoffice/workspace'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; -export const isDataTypeVariantContext = (context: UmbVariantContext): context is UmbDataTypeVariantContext => ('properties' in context && context.getType() === 'data-type'); +export const isDataTypeVariantContext = (context: UmbVariantContext): context is UmbDataTypeVariantContext => + 'properties' in context && context.getType() === 'data-type'; export const UMB_DATA_TYPE_VARIANT_CONTEXT = new UmbContextToken( - "UmbVariantContext", isDataTypeVariantContext); + 'UmbVariantContext', + 'default', + isDataTypeVariantContext, +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.context.ts index 7e249b245e..f71e75e0c0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.context.ts @@ -265,5 +265,6 @@ export const UMB_DATA_TYPE_WORKSPACE_CONTEXT = new UmbContextToken< UmbDataTypeWorkspaceContext >( 'UmbWorkspaceContext', + 'default', (context): context is UmbDataTypeWorkspaceContext => context.getEntityType?.() === 'data-type', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/nameable-variant-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/nameable-variant-context.token.ts index 307962082f..6fe922324b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/nameable-variant-context.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/nameable-variant-context.token.ts @@ -1,9 +1,12 @@ -import { type UmbVariantContext } from "./variant-context.interface.js"; -import { UmbNameableVariantContext } from "./nameable-variant-context.interface.js"; -import { UmbContextToken } from "@umbraco-cms/backoffice/context-api"; +import { type UmbVariantContext } from './variant-context.interface.js'; +import { UmbNameableVariantContext } from './nameable-variant-context.interface.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; -export const isNameablePropertySetContext = (context: UmbVariantContext): context is UmbNameableVariantContext => 'setName' in context; +export const isNameablePropertySetContext = (context: UmbVariantContext): context is UmbNameableVariantContext => + 'setName' in context; export const UMB_NAMEABLE_VARIANT_CONTEXT = new UmbContextToken( - "UmbVariantContext", - isNameablePropertySetContext); + 'UmbVariantContext', + 'default', + isNameablePropertySetContext, +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/variant-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/variant-context.token.ts index 1827b68e81..bb33a6442a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/variant-context.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/variant-context.token.ts @@ -1,4 +1,4 @@ -import { type UmbVariantContext } from "./variant-context.interface.js"; -import { UmbContextToken } from "@umbraco-cms/backoffice/context-api"; +import { type UmbVariantContext } from './variant-context.interface.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; -export const UMB_VARIANT_CONTEXT = new UmbContextToken("UmbVariantContext"); +export const UMB_VARIANT_CONTEXT = new UmbContextToken('UmbVariantContext'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/variant-workspace-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/variant-workspace-context.token.ts index 16ebe6b2cd..0e5012a346 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/variant-workspace-context.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/variant-workspace-context.token.ts @@ -6,4 +6,8 @@ import type { UmbEntityBase } from '@umbraco-cms/backoffice/models'; export const UMB_VARIANT_WORKSPACE_CONTEXT_TOKEN = new UmbContextToken< UmbWorkspaceContextInterface, UmbVariantableWorkspaceContextInterface ->('UmbWorkspaceContext', (context): context is UmbVariantableWorkspaceContextInterface => 'variants' in context); +>( + 'UmbWorkspaceContext', + 'default', + (context): context is UmbVariantableWorkspaceContextInterface => 'variants' in context, +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/workspace/dictionary-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/workspace/dictionary-workspace.context.ts index 492b157e66..8d97b7d2ee 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/workspace/dictionary-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/workspace/dictionary-workspace.context.ts @@ -1,5 +1,8 @@ import { UmbDictionaryRepository } from '../repository/dictionary.repository.js'; -import { UmbSaveableWorkspaceContextInterface, UmbEditableWorkspaceContextBase } from '@umbraco-cms/backoffice/workspace'; +import { + UmbSaveableWorkspaceContextInterface, + UmbEditableWorkspaceContextBase, +} from '@umbraco-cms/backoffice/workspace'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { DictionaryItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; @@ -97,5 +100,6 @@ export const UMB_DICTIONARY_WORKSPACE_CONTEXT = new UmbContextToken< UmbDictionaryWorkspaceContext >( 'UmbWorkspaceContext', + 'default', (context): context is UmbDictionaryWorkspaceContext => context.getEntityType?.() === 'dictionary-item', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.context.ts index 82b49c3a00..b5b5b62a0e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.context.ts @@ -176,5 +176,6 @@ export const UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT = new UmbContextToken< UmbDocumentTypeWorkspaceContext >( 'UmbWorkspaceContext', + 'default', (context): context is UmbDocumentTypeWorkspaceContext => context.getEntityType?.() === 'document-type', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/variant-context/document-variant-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/variant-context/document-variant-context.token.ts index 2d97cee2b8..8cbefdcc82 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/variant-context/document-variant-context.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/variant-context/document-variant-context.token.ts @@ -8,5 +8,6 @@ export const IsDocumentVariantContext = (context: UmbVariantContext): context is export const UMB_DOCUMENT_VARIANT_CONTEXT = new UmbContextToken( 'UmbVariantContext', + 'default', IsDocumentVariantContext, ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index ba169380b1..12cc53d2b0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -235,6 +235,6 @@ export const UMB_DOCUMENT_WORKSPACE_CONTEXT = new UmbContextToken< UmbDocumentWorkspaceContext >( 'UmbWorkspaceContext', - // TODO: Refactor: make a better generic way to identify workspaces, maybe workspaceType or workspaceAlias?. + 'default', (context): context is UmbDocumentWorkspaceContext => context.getEntityType?.() === UMB_DOCUMENT_ENTITY_TYPE, ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts index f5ababbdad..49519549f7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts @@ -1,5 +1,8 @@ import { UmbMediaTypeRepository } from '../repository/media-type.repository.js'; -import { UmbSaveableWorkspaceContextInterface, UmbEditableWorkspaceContextBase } from '@umbraco-cms/backoffice/workspace'; +import { + UmbSaveableWorkspaceContextInterface, + UmbEditableWorkspaceContextBase, +} from '@umbraco-cms/backoffice/workspace'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; @@ -83,5 +86,6 @@ export const UMB_MEDIA_TYPE_WORKSPACE_CONTEXT = new UmbContextToken< UmbMediaTypeWorkspaceContext >( 'UmbWorkspaceContext', + 'default', (context): context is UmbMediaTypeWorkspaceContext => context.getEntityType?.() === 'media-type', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts index 64aab3cac8..e855ea90e5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts @@ -89,4 +89,8 @@ export class UmbMediaWorkspaceContext export const UMB_MEDIA_WORKSPACE_CONTEXT = new UmbContextToken< UmbSaveableWorkspaceContextInterface, UmbMediaWorkspaceContext ->('UmbWorkspaceContext', (context): context is UmbMediaWorkspaceContext => context.getEntityType?.() === 'media'); +>( + 'UmbWorkspaceContext', + 'default', + (context): context is UmbMediaWorkspaceContext => context.getEntityType?.() === 'media', +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-groups/workspace/member-group-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-groups/workspace/member-group-workspace.context.ts index 36a48e1b06..9a6436e6b9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-groups/workspace/member-group-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-groups/workspace/member-group-workspace.context.ts @@ -47,5 +47,6 @@ export const UMB_MEMBER_GROUP_WORKSPACE_CONTEXT = new UmbContextToken< UmbMemberGroupWorkspaceContext >( 'UmbWorkspaceContext', + 'default', (context): context is UmbMemberGroupWorkspaceContext => context.getEntityType?.() === UMB_MEMBER_GROUP_ENTITY_TYPE, ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/workspace/member-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/workspace/member-type-workspace.context.ts index 8063754df5..2fd8778f83 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/workspace/member-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/workspace/member-type-workspace.context.ts @@ -1,5 +1,8 @@ import { UmbMemberTypeRepository } from '../repository/member-type.repository.js'; -import { UmbSaveableWorkspaceContextInterface, UmbEditableWorkspaceContextBase } from '@umbraco-cms/backoffice/workspace'; +import { + UmbSaveableWorkspaceContextInterface, + UmbEditableWorkspaceContextBase, +} from '@umbraco-cms/backoffice/workspace'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; @@ -75,7 +78,11 @@ export class UmbMemberTypeWorkspaceContext } } -export const UMB_MEMBER_TYPE_WORKSPACE_CONTEXT = new UmbContextToken( +export const UMB_MEMBER_TYPE_WORKSPACE_CONTEXT = new UmbContextToken< + UmbSaveableWorkspaceContextInterface, + UmbMemberTypeWorkspaceContext +>( 'UmbWorkspaceContext', - (context): context is UmbMemberTypeWorkspaceContext => context.getEntityType?.() === 'member-type' + 'default', + (context): context is UmbMemberTypeWorkspaceContext => context.getEntityType?.() === 'member-type', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/members/workspace/member-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/members/workspace/member-workspace.context.ts index 986f6adc3a..f1b88644d7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/members/workspace/member-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/members/workspace/member-workspace.context.ts @@ -47,5 +47,6 @@ export const UMB_MEMBER_WORKSPACE_CONTEXT = new UmbContextToken< UmbMemberWorkspaceContext >( 'UmbWorkspaceContext', + 'default', (context): context is UmbMemberWorkspaceContext => context.getEntityType?.() === UMB_MEMBER_ENTITY_TYPE, ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.context.ts index 9c6626a865..77fc85f1fd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.context.ts @@ -1,5 +1,8 @@ import { UmbLanguageRepository } from '../../repository/language.repository.js'; -import { UmbSaveableWorkspaceContextInterface, UmbEditableWorkspaceContextBase } from '@umbraco-cms/backoffice/workspace'; +import { + UmbSaveableWorkspaceContextInterface, + UmbEditableWorkspaceContextBase, +} from '@umbraco-cms/backoffice/workspace'; import { ApiError, LanguageResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; @@ -102,8 +105,11 @@ export class UmbLanguageWorkspaceContext } } - -export const UMB_LANGUAGE_WORKSPACE_CONTEXT = new UmbContextToken( +export const UMB_LANGUAGE_WORKSPACE_CONTEXT = new UmbContextToken< + UmbSaveableWorkspaceContextInterface, + UmbLanguageWorkspaceContext +>( 'UmbWorkspaceContext', - (context): context is UmbLanguageWorkspaceContext => context.getEntityType?.() === 'language' + 'default', + (context): context is UmbLanguageWorkspaceContext => context.getEntityType?.() === 'language', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/relation-types/workspace/relation-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/relation-types/workspace/relation-type-workspace.context.ts index 237d6a83a5..85586bbb4e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/relation-types/workspace/relation-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/relation-types/workspace/relation-type-workspace.context.ts @@ -1,5 +1,8 @@ import { UmbRelationTypeRepository } from '../repository/relation-type.repository.js'; -import { UmbSaveableWorkspaceContextInterface, UmbEditableWorkspaceContextBase } from '@umbraco-cms/backoffice/workspace'; +import { + UmbSaveableWorkspaceContextInterface, + UmbEditableWorkspaceContextBase, +} from '@umbraco-cms/backoffice/workspace'; import type { RelationTypeBaseModel, RelationTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; @@ -77,9 +80,11 @@ export class UmbRelationTypeWorkspaceContext } } - - -export const UMB_RELATION_TYPE_WORKSPACE_CONTEXT = new UmbContextToken( +export const UMB_RELATION_TYPE_WORKSPACE_CONTEXT = new UmbContextToken< + UmbSaveableWorkspaceContextInterface, + UmbRelationTypeWorkspaceContext +>( 'UmbWorkspaceContext', - (context): context is UmbRelationTypeWorkspaceContext => context.getEntityType?.() === 'relation-type' + 'default', + (context): context is UmbRelationTypeWorkspaceContext => context.getEntityType?.() === 'relation-type', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/partial-view-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/partial-view-workspace.context.ts index 0b925b21c6..0fdc98c256 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/partial-view-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/partial-view-workspace.context.ts @@ -107,5 +107,6 @@ export const UMB_PARTIAL_VIEW_WORKSPACE_CONTEXT = new UmbContextToken< UmbPartialViewWorkspaceContext >( 'UmbWorkspaceContext', + 'default', (context): context is UmbPartialViewWorkspaceContext => context.getEntityType?.() === UMB_PARTIAL_VIEW_ENTITY_TYPE, ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts index 159cd65722..1e4c8a9aa3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts @@ -189,5 +189,6 @@ export const UMB_STYLESHEET_WORKSPACE_CONTEXT = new UmbContextToken< UmbStylesheetWorkspaceContext >( 'UmbWorkspaceContext', + 'default', (context): context is UmbStylesheetWorkspaceContext => context.getEntityType?.() === 'stylesheet', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/template-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/template-workspace.context.ts index ae77ddbd4b..0d5b332168 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/template-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/template-workspace.context.ts @@ -176,4 +176,8 @@ ${currentContent}`; export const UMB_TEMPLATE_WORKSPACE_CONTEXT = new UmbContextToken< UmbSaveableWorkspaceContextInterface, UmbTemplateWorkspaceContext ->('UmbWorkspaceContext', (context): context is UmbTemplateWorkspaceContext => context.getEntityType?.() === 'template'); +>( + 'UmbWorkspaceContext', + 'default', + (context): context is UmbTemplateWorkspaceContext => context.getEntityType?.() === 'template', +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace.context.ts index 07af16b3a1..388cb3edac 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace.context.ts @@ -1,6 +1,9 @@ import { UmbUserGroupRepository } from '../repository/user-group.repository.js'; import { UmbUserRepository } from '../../user/repository/user.repository.js'; -import { UmbSaveableWorkspaceContextInterface, UmbEditableWorkspaceContextBase } from '@umbraco-cms/backoffice/workspace'; +import { + UmbSaveableWorkspaceContextInterface, + UmbEditableWorkspaceContextBase, +} from '@umbraco-cms/backoffice/workspace'; import type { UserGroupResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbArrayState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; @@ -153,5 +156,6 @@ export const UMB_USER_GROUP_WORKSPACE_CONTEXT = new UmbContextToken< UmbUserGroupWorkspaceContext >( 'UmbWorkspaceContext', + 'default', (context): context is UmbUserGroupWorkspaceContext => context.getEntityType?.() === 'user-group', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts index 9a3c410fa1..7469d3e1f2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts @@ -115,5 +115,6 @@ export const UMB_USER_WORKSPACE_CONTEXT = new UmbContextToken< UmbUserWorkspaceContext >( 'UmbWorkspaceContext', + 'default', (context): context is UmbUserWorkspaceContext => context.getEntityType?.() === UMB_USER_ENTITY_TYPE, ); From 7722da0d4f05ec4c336ccd80bfd3ea7f594aec8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 21 Nov 2023 11:45:54 +0100 Subject: [PATCH 16/25] tests --- .../consume/context-consumer.test.ts | 6 +- .../consume/context-request.event.test.ts | 9 +- .../provide/context-provider.test.ts | 5 +- .../context-api/token/context-token.test.ts | 183 ++++++++++++++---- 4 files changed, 158 insertions(+), 45 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.test.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.test.ts index edb6435a95..986a878cee 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.test.ts @@ -112,7 +112,7 @@ describe('UmbContextConsumer with discriminator test', () => { it('discriminator determines the instance type', async () => { const localConsumer = new UmbContextConsumer( document.body, - new UmbContextToken(testContextAlias, discriminator), + new UmbContextToken(testContextAlias, undefined, discriminator), (instance: A) => { console.log(instance); }, @@ -136,7 +136,7 @@ describe('UmbContextConsumer with discriminator test', () => { const localConsumer = new UmbContextConsumer( element, - new UmbContextToken(testContextAlias, discriminator), + new UmbContextToken(testContextAlias, undefined, discriminator), (_instance) => { expect(_instance.prop).to.eq('value from provider'); done(); @@ -156,7 +156,7 @@ describe('UmbContextConsumer with discriminator test', () => { const localConsumer = new UmbContextConsumer( element, - new UmbContextToken(testContextAlias, badDiscriminator), + new UmbContextToken(testContextAlias, undefined, badDiscriminator), (_instance) => { expect(_instance.prop).to.eq('this must not happen!'); }, diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-request.event.test.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-request.event.test.ts index f6557a2a75..ee1cb0162e 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-request.event.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-request.event.test.ts @@ -9,13 +9,18 @@ describe('UmbContextRequestEvent', () => { const event: UmbContextRequestEvent = new UmbContextRequestEventImplementation( 'my-test-context-alias', - contextRequestCallback + 'my-test-api-alias', + contextRequestCallback, ); - it('has context', () => { + it('has context alias', () => { expect(event.contextAlias).to.eq('my-test-context-alias'); }); + it('has api alias', () => { + expect(event.apiAlias).to.eq('my-test-api-alias'); + }); + it('has a callback', () => { expect(event.callback).to.eq(contextRequestCallback); }); diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.test.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.test.ts index e8ff8bb119..5090598136 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/provide/context-provider.test.ts @@ -42,11 +42,12 @@ describe('UmbContextProvider', () => { it('handles context request events', (done) => { const event = new UmbContextRequestEventImplementation( 'my-test-context', + 'default', (_instance: UmbTestContextProviderClass) => { expect(_instance.prop).to.eq('value from provider'); done(); return true; - } + }, ); document.body.dispatchEvent(event); @@ -63,7 +64,7 @@ describe('UmbContextProvider', () => { expect(_instance?.prop).to.eq('value from provider'); done(); localConsumer.hostDisconnected(); - } + }, ); localConsumer.hostConnected(); }); diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/token/context-token.test.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/token/context-token.test.ts index 678534bec8..882442a1d9 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/token/context-token.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/token/context-token.test.ts @@ -4,57 +4,164 @@ import { UmbContextProvider } from '../provide/context-provider.js'; import { UmbContextToken } from './context-token.js'; const testContextAlias = 'my-test-context'; +const testApiAlias = 'my-test-api'; class UmbTestContextTokenClass { prop = 'value from provider'; } describe('UmbContextToken', () => { - const contextToken = new UmbContextToken(testContextAlias); - const typedProvider = new UmbContextProvider(document.body, contextToken, new UmbTestContextTokenClass()); - typedProvider.hostConnected(); + describe('Simple context token', () => { + const contextToken = new UmbContextToken(testContextAlias); + const typedProvider = new UmbContextProvider(document.body, contextToken, new UmbTestContextTokenClass()); + typedProvider.hostConnected(); - after(() => { - typedProvider.hostDisconnected(); + after(() => { + typedProvider.hostDisconnected(); + }); + + it('toString returns the alias', () => { + expect(contextToken.toString()).to.eq(testContextAlias + '#' + 'default'); + }); + + it('can be used to consume a context API', (done) => { + const element = document.createElement('div'); + document.body.appendChild(element); + + const localConsumer = new UmbContextConsumer( + element, + contextToken, + (_instance: UmbTestContextTokenClass | undefined) => { + expect(_instance).to.be.instanceOf(UmbTestContextTokenClass); + expect(_instance?.prop).to.eq('value from provider'); + done(); + localConsumer.destroy(); // We do not want to react to when the provider is disconnected. + }, + ); + + localConsumer.hostConnected(); + }); + + it('gives the same result when using the string alias', (done) => { + const element = document.createElement('div'); + document.body.appendChild(element); + + const localConsumer = new UmbContextConsumer( + element, + testContextAlias, + (_instance: UmbTestContextTokenClass | undefined) => { + expect(_instance).to.be.instanceOf(UmbTestContextTokenClass); + expect(_instance?.prop).to.eq('value from provider'); + done(); + localConsumer.destroy(); // We do not want to react to when the provider is disconnected. + }, + ); + + localConsumer.hostConnected(); + }); }); - it('toString returns the alias', () => { - expect(contextToken.toString()).to.eq(testContextAlias); + describe('Context Token with alternative api alias', () => { + const contextToken = new UmbContextToken(testContextAlias, testApiAlias); + const typedProvider = new UmbContextProvider(document.body, contextToken, new UmbTestContextTokenClass()); + typedProvider.hostConnected(); + + after(() => { + typedProvider.hostDisconnected(); + }); + + it('toString returns the alias', () => { + expect(contextToken.toString()).to.eq(testContextAlias + '#' + testApiAlias); + }); + + it('can be used to consume a context API', (done) => { + const element = document.createElement('div'); + document.body.appendChild(element); + + const localConsumer = new UmbContextConsumer( + element, + contextToken, + (_instance: UmbTestContextTokenClass | undefined) => { + expect(_instance).to.be.instanceOf(UmbTestContextTokenClass); + expect(_instance?.prop).to.eq('value from provider'); + done(); + localConsumer.destroy(); // We do not want to react to when the provider is disconnected. + }, + ); + + localConsumer.hostConnected(); + }); + + it('gives the same result when using the string alias', (done) => { + const element = document.createElement('div'); + document.body.appendChild(element); + + const localConsumer = new UmbContextConsumer( + element, + testContextAlias, + (_instance: UmbTestContextTokenClass | undefined) => { + expect(_instance).to.be.instanceOf(UmbTestContextTokenClass); + expect(_instance?.prop).to.eq('value from provider'); + done(); + localConsumer.destroy(); // We do not want to react to when the provider is disconnected. + }, + ); + + localConsumer.hostConnected(); + }); }); - it('can be used to consume a context API', (done) => { - const element = document.createElement('div'); - document.body.appendChild(element); - - const localConsumer = new UmbContextConsumer( - element, - contextToken, - (_instance: UmbTestContextTokenClass | undefined) => { - expect(_instance).to.be.instanceOf(UmbTestContextTokenClass); - expect(_instance?.prop).to.eq('value from provider'); - done(); - localConsumer.destroy(); // We do not want to react to when the provider is disconnected. - } - ); - - localConsumer.hostConnected(); - }); - - it('gives the same result when using the string alias', (done) => { - const element = document.createElement('div'); - document.body.appendChild(element); - - const localConsumer = new UmbContextConsumer( - element, + describe('Context Token with discriminator method', () => { + const contextToken = new UmbContextToken( testContextAlias, - (_instance: UmbTestContextTokenClass | undefined) => { - expect(_instance).to.be.instanceOf(UmbTestContextTokenClass); - expect(_instance?.prop).to.eq('value from provider'); - done(); - localConsumer.destroy(); // We do not want to react to when the provider is disconnected. - } + undefined, + (instance): instance is UmbTestContextTokenClass => instance.prop === 'value from provider', ); + const typedProvider = new UmbContextProvider(document.body, contextToken, new UmbTestContextTokenClass()); + typedProvider.hostConnected(); - localConsumer.hostConnected(); + after(() => { + typedProvider.hostDisconnected(); + }); + + it('toString returns the alias', () => { + expect(contextToken.toString()).to.eq(testContextAlias + '#' + 'default'); + }); + + it('can be used to consume a context API', (done) => { + const element = document.createElement('div'); + document.body.appendChild(element); + + const localConsumer = new UmbContextConsumer( + element, + contextToken, + (_instance: UmbTestContextTokenClass | undefined) => { + expect(_instance).to.be.instanceOf(UmbTestContextTokenClass); + expect(_instance?.prop).to.eq('value from provider'); + done(); + localConsumer.destroy(); // We do not want to react to when the provider is disconnected. + }, + ); + + localConsumer.hostConnected(); + }); + + it('gives the same result when using the string alias', (done) => { + const element = document.createElement('div'); + document.body.appendChild(element); + + const localConsumer = new UmbContextConsumer( + element, + testContextAlias, + (_instance: UmbTestContextTokenClass | undefined) => { + expect(_instance).to.be.instanceOf(UmbTestContextTokenClass); + expect(_instance?.prop).to.eq('value from provider'); + done(); + localConsumer.destroy(); // We do not want to react to when the provider is disconnected. + }, + ); + + localConsumer.hostConnected(); + }); }); }); From 692ff08250aee762638f5a2200d1f2b982a07b1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 21 Nov 2023 12:51:58 +0100 Subject: [PATCH 17/25] use undefined to get default --- .../variant-context/data-type-variant-context.token.ts | 2 +- .../core/data-type/workspace/data-type-workspace.context.ts | 2 +- .../workspace/variant-context/nameable-variant-context.token.ts | 2 +- .../workspace-context/variant-workspace-context.token.ts | 2 +- .../dictionary/workspace/dictionary-workspace.context.ts | 2 +- .../document-types/workspace/document-type-workspace.context.ts | 2 +- .../documents/variant-context/document-variant-context.token.ts | 2 +- .../documents/documents/workspace/document-workspace.context.ts | 2 +- .../media/media-types/workspace/media-type-workspace.context.ts | 2 +- .../packages/media/media/workspace/media-workspace.context.ts | 2 +- .../member-groups/workspace/member-group-workspace.context.ts | 2 +- .../member-types/workspace/member-type-workspace.context.ts | 2 +- .../members/members/workspace/member-workspace.context.ts | 2 +- .../languages/workspace/language/language-workspace.context.ts | 2 +- .../relation-types/workspace/relation-type-workspace.context.ts | 2 +- .../partial-views/workspace/partial-view-workspace.context.ts | 2 +- .../stylesheets/workspace/stylesheet-workspace.context.ts | 2 +- .../templates/workspace/template-workspace.context.ts | 2 +- .../user/user-group/workspace/user-group-workspace.context.ts | 2 +- .../src/packages/user/user/workspace/user-workspace.context.ts | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/variant-context/data-type-variant-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/variant-context/data-type-variant-context.token.ts index b30be8a749..465fdb7ec6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/variant-context/data-type-variant-context.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/variant-context/data-type-variant-context.token.ts @@ -7,6 +7,6 @@ export const isDataTypeVariantContext = (context: UmbVariantContext): context is export const UMB_DATA_TYPE_VARIANT_CONTEXT = new UmbContextToken( 'UmbVariantContext', - 'default', + undefined, isDataTypeVariantContext, ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.context.ts index f71e75e0c0..21a2f7af84 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.context.ts @@ -265,6 +265,6 @@ export const UMB_DATA_TYPE_WORKSPACE_CONTEXT = new UmbContextToken< UmbDataTypeWorkspaceContext >( 'UmbWorkspaceContext', - 'default', + undefined, (context): context is UmbDataTypeWorkspaceContext => context.getEntityType?.() === 'data-type', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/nameable-variant-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/nameable-variant-context.token.ts index 6fe922324b..c4386e9bfe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/nameable-variant-context.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/variant-context/nameable-variant-context.token.ts @@ -7,6 +7,6 @@ export const isNameablePropertySetContext = (context: UmbVariantContext): contex export const UMB_NAMEABLE_VARIANT_CONTEXT = new UmbContextToken( 'UmbVariantContext', - 'default', + undefined, isNameablePropertySetContext, ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/variant-workspace-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/variant-workspace-context.token.ts index 0e5012a346..e09238e6dc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/variant-workspace-context.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/variant-workspace-context.token.ts @@ -8,6 +8,6 @@ export const UMB_VARIANT_WORKSPACE_CONTEXT_TOKEN = new UmbContextToken< UmbVariantableWorkspaceContextInterface >( 'UmbWorkspaceContext', - 'default', + undefined, (context): context is UmbVariantableWorkspaceContextInterface => 'variants' in context, ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/workspace/dictionary-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/workspace/dictionary-workspace.context.ts index 8d97b7d2ee..997db59a7d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/workspace/dictionary-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/workspace/dictionary-workspace.context.ts @@ -100,6 +100,6 @@ export const UMB_DICTIONARY_WORKSPACE_CONTEXT = new UmbContextToken< UmbDictionaryWorkspaceContext >( 'UmbWorkspaceContext', - 'default', + undefined, (context): context is UmbDictionaryWorkspaceContext => context.getEntityType?.() === 'dictionary-item', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.context.ts index b5b5b62a0e..c39891181c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace.context.ts @@ -176,6 +176,6 @@ export const UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT = new UmbContextToken< UmbDocumentTypeWorkspaceContext >( 'UmbWorkspaceContext', - 'default', + undefined, (context): context is UmbDocumentTypeWorkspaceContext => context.getEntityType?.() === 'document-type', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/variant-context/document-variant-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/variant-context/document-variant-context.token.ts index 8cbefdcc82..e5ecd6f911 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/variant-context/document-variant-context.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/variant-context/document-variant-context.token.ts @@ -8,6 +8,6 @@ export const IsDocumentVariantContext = (context: UmbVariantContext): context is export const UMB_DOCUMENT_VARIANT_CONTEXT = new UmbContextToken( 'UmbVariantContext', - 'default', + undefined, IsDocumentVariantContext, ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 12cc53d2b0..0b2f8561fd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -235,6 +235,6 @@ export const UMB_DOCUMENT_WORKSPACE_CONTEXT = new UmbContextToken< UmbDocumentWorkspaceContext >( 'UmbWorkspaceContext', - 'default', + undefined, (context): context is UmbDocumentWorkspaceContext => context.getEntityType?.() === UMB_DOCUMENT_ENTITY_TYPE, ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts index 49519549f7..e8ab17ba2c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts @@ -86,6 +86,6 @@ export const UMB_MEDIA_TYPE_WORKSPACE_CONTEXT = new UmbContextToken< UmbMediaTypeWorkspaceContext >( 'UmbWorkspaceContext', - 'default', + undefined, (context): context is UmbMediaTypeWorkspaceContext => context.getEntityType?.() === 'media-type', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts index e855ea90e5..65db834f3b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts @@ -91,6 +91,6 @@ export const UMB_MEDIA_WORKSPACE_CONTEXT = new UmbContextToken< UmbMediaWorkspaceContext >( 'UmbWorkspaceContext', - 'default', + undefined, (context): context is UmbMediaWorkspaceContext => context.getEntityType?.() === 'media', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-groups/workspace/member-group-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-groups/workspace/member-group-workspace.context.ts index 9a6436e6b9..38da5949c0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-groups/workspace/member-group-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-groups/workspace/member-group-workspace.context.ts @@ -47,6 +47,6 @@ export const UMB_MEMBER_GROUP_WORKSPACE_CONTEXT = new UmbContextToken< UmbMemberGroupWorkspaceContext >( 'UmbWorkspaceContext', - 'default', + undefined, (context): context is UmbMemberGroupWorkspaceContext => context.getEntityType?.() === UMB_MEMBER_GROUP_ENTITY_TYPE, ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/workspace/member-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/workspace/member-type-workspace.context.ts index 2fd8778f83..1ce66f3df7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/workspace/member-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/workspace/member-type-workspace.context.ts @@ -83,6 +83,6 @@ export const UMB_MEMBER_TYPE_WORKSPACE_CONTEXT = new UmbContextToken< UmbMemberTypeWorkspaceContext >( 'UmbWorkspaceContext', - 'default', + undefined, (context): context is UmbMemberTypeWorkspaceContext => context.getEntityType?.() === 'member-type', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/members/workspace/member-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/members/workspace/member-workspace.context.ts index f1b88644d7..ad6c12f22c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/members/workspace/member-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/members/workspace/member-workspace.context.ts @@ -47,6 +47,6 @@ export const UMB_MEMBER_WORKSPACE_CONTEXT = new UmbContextToken< UmbMemberWorkspaceContext >( 'UmbWorkspaceContext', - 'default', + undefined, (context): context is UmbMemberWorkspaceContext => context.getEntityType?.() === UMB_MEMBER_ENTITY_TYPE, ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.context.ts index 77fc85f1fd..b0f89da15f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.context.ts @@ -110,6 +110,6 @@ export const UMB_LANGUAGE_WORKSPACE_CONTEXT = new UmbContextToken< UmbLanguageWorkspaceContext >( 'UmbWorkspaceContext', - 'default', + undefined, (context): context is UmbLanguageWorkspaceContext => context.getEntityType?.() === 'language', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/relation-types/workspace/relation-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/relation-types/workspace/relation-type-workspace.context.ts index 85586bbb4e..d4be3a51e0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/relation-types/workspace/relation-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/relation-types/workspace/relation-type-workspace.context.ts @@ -85,6 +85,6 @@ export const UMB_RELATION_TYPE_WORKSPACE_CONTEXT = new UmbContextToken< UmbRelationTypeWorkspaceContext >( 'UmbWorkspaceContext', - 'default', + undefined, (context): context is UmbRelationTypeWorkspaceContext => context.getEntityType?.() === 'relation-type', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/partial-view-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/partial-view-workspace.context.ts index 0fdc98c256..6b818a1702 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/partial-view-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/partial-view-workspace.context.ts @@ -107,6 +107,6 @@ export const UMB_PARTIAL_VIEW_WORKSPACE_CONTEXT = new UmbContextToken< UmbPartialViewWorkspaceContext >( 'UmbWorkspaceContext', - 'default', + undefined, (context): context is UmbPartialViewWorkspaceContext => context.getEntityType?.() === UMB_PARTIAL_VIEW_ENTITY_TYPE, ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts index 1e4c8a9aa3..512d396a11 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts @@ -189,6 +189,6 @@ export const UMB_STYLESHEET_WORKSPACE_CONTEXT = new UmbContextToken< UmbStylesheetWorkspaceContext >( 'UmbWorkspaceContext', - 'default', + undefined, (context): context is UmbStylesheetWorkspaceContext => context.getEntityType?.() === 'stylesheet', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/template-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/template-workspace.context.ts index 0d5b332168..c9f1f93c24 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/template-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/template-workspace.context.ts @@ -178,6 +178,6 @@ export const UMB_TEMPLATE_WORKSPACE_CONTEXT = new UmbContextToken< UmbTemplateWorkspaceContext >( 'UmbWorkspaceContext', - 'default', + undefined, (context): context is UmbTemplateWorkspaceContext => context.getEntityType?.() === 'template', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace.context.ts index 388cb3edac..15384293e0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace.context.ts @@ -156,6 +156,6 @@ export const UMB_USER_GROUP_WORKSPACE_CONTEXT = new UmbContextToken< UmbUserGroupWorkspaceContext >( 'UmbWorkspaceContext', - 'default', + undefined, (context): context is UmbUserGroupWorkspaceContext => context.getEntityType?.() === 'user-group', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts index 7469d3e1f2..11ab943a6c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user-workspace.context.ts @@ -115,6 +115,6 @@ export const UMB_USER_WORKSPACE_CONTEXT = new UmbContextToken< UmbUserWorkspaceContext >( 'UmbWorkspaceContext', - 'default', + undefined, (context): context is UmbUserWorkspaceContext => context.getEntityType?.() === UMB_USER_ENTITY_TYPE, ); From e2c3c99ffd463f22d4d671175fef45d5c38c4dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 21 Nov 2023 15:01:27 +0100 Subject: [PATCH 18/25] remove log and comment --- .../workspace/document-workspace.element.ts | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.element.ts index c1d3c4e6e2..3c6d303203 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.element.ts @@ -1,5 +1,5 @@ import type { UmbDocumentWorkspaceContext } from './document-workspace.context.js'; -import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import type { UmbRoute } from '@umbraco-cms/backoffice/router'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; @@ -11,26 +11,18 @@ import { ManifestWorkspace, umbExtensionsRegistry } from '@umbraco-cms/backoffic @customElement('umb-document-workspace') export class UmbDocumentWorkspaceElement extends UmbLitElement { - - #workspaceContext?: UmbDocumentWorkspaceContext; - @state() _routes: UmbRoute[] = []; public set manifest(manifest: ManifestWorkspace) { - - console.log("got manifest", manifest.alias) - // TODO: Make context declaration. - - createExtensionApi(manifest, [this]).then( (context) => { - if(context) { + createExtensionApi(manifest, [this]).then((context) => { + if (context) { this.#gotWorkspaceContext(context); } - }) - - }; + }); + } #gotWorkspaceContext(context: UmbApi) { this.#workspaceContext = context as UmbDocumentWorkspaceContext; @@ -44,11 +36,11 @@ export class UmbDocumentWorkspaceElement extends UmbLitElement { const parentId = info.match.params.parentId === 'null' ? null : info.match.params.parentId; const documentTypeKey = info.match.params.documentTypeKey; this.#workspaceContext!.create(documentTypeKey, parentId); - + new UmbWorkspaceIsNewRedirectController( this, this.#workspaceContext!, - this.shadowRoot!.querySelector('umb-router-slot')! + this.shadowRoot!.querySelector('umb-router-slot')!, ); }, }, From 75f0ba87f464fe900babd31146e372d58b85d126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 21 Nov 2023 20:33:39 +0100 Subject: [PATCH 19/25] better tests --- .../consume/context-consumer.test.ts | 282 +++++++++++------- 1 file changed, 167 insertions(+), 115 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.test.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.test.ts index 986a878cee..80b1df93b1 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.test.ts @@ -5,20 +5,22 @@ import { UmbContextConsumer } from './context-consumer.js'; import { UmbContextRequestEventImplementation, UMB_CONTENT_REQUEST_EVENT_TYPE } from './context-request.event.js'; const testContextAlias = 'my-test-context'; +const testContextAliasAndApiAlias = 'my-test-context#testApi'; +const testContextAliasAndNotExstingApiAlias = 'my-test-context#notExistingTestApi'; class UmbTestContextConsumerClass { public prop: string = 'value from provider'; } describe('UmbContextConsumer', () => { - let consumer: UmbContextConsumer; - - beforeEach(() => { - // eslint-disable-next-line @typescript-eslint/no-empty-function - consumer = new UmbContextConsumer(document.body, testContextAlias, () => {}); - }); - describe('Public API', () => { + let consumer: UmbContextConsumer; + + beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/no-empty-function + consumer = new UmbContextConsumer(document.body, testContextAlias, () => {}); + }); + describe('properties', () => { it('has a instance property', () => { expect(consumer).to.have.property('instance').that.is.undefined; @@ -45,128 +47,178 @@ describe('UmbContextConsumer', () => { }); }); - it('works with UmbContextProvider', (done) => { - const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass()); - provider.hostConnected(); + describe('Simple implementation', () => { + it('works with UmbContextProvider', (done) => { + const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass()); + provider.hostConnected(); - const element = document.createElement('div'); - document.body.appendChild(element); + const element = document.createElement('div'); + document.body.appendChild(element); - const localConsumer = new UmbContextConsumer( - element, - testContextAlias, - (_instance: UmbTestContextConsumerClass | undefined) => { + const localConsumer = new UmbContextConsumer( + element, + testContextAlias, + (_instance: UmbTestContextConsumerClass | undefined) => { + if (_instance) { + expect(_instance.prop).to.eq('value from provider'); + done(); + localConsumer.hostDisconnected(); + provider.hostDisconnected(); + } + }, + ); + localConsumer.hostConnected(); + }); + + /* + Unprovided feature is out commented currently. I'm not sure there is a use case. So lets leave the code around until we know for sure. + it('acts to Context API disconnected', (done) => { + const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass()); + provider.hostConnected(); + + const element = document.createElement('div'); + document.body.appendChild(element); + + let callbackNum = 0; + + const localConsumer = new UmbContextConsumer( + element, + testContextAlias, + (_instance: UmbTestContextConsumerClass | undefined) => { + callbackNum++; + if (callbackNum === 1) { + expect(_instance?.prop).to.eq('value from provider'); + // unregister. + provider.hostDisconnected(); + } else if (callbackNum === 2) { + expect(_instance?.prop).to.be.undefined; + done(); + } + } + ); + localConsumer.hostConnected(); + }); + */ + }); + + describe('Implementation with Api Alias', () => { + it('responds when api alias matches', (done) => { + const provider = new UmbContextProvider( + document.body, + testContextAliasAndApiAlias, + new UmbTestContextConsumerClass(), + ); + provider.hostConnected(); + + const element = document.createElement('div'); + document.body.appendChild(element); + + const localConsumer = new UmbContextConsumer(element, testContextAliasAndApiAlias, (_instance) => { if (_instance) { + expect((_instance as UmbTestContextConsumerClass).prop).to.eq('value from provider'); + localConsumer.hostDisconnected(); + provider.hostDisconnected(); + done(); + } + }); + localConsumer.hostConnected(); + }); + + it('does not respond to a non existing api alias', (done) => { + const provider = new UmbContextProvider( + document.body, + testContextAliasAndApiAlias, + new UmbTestContextConsumerClass(), + ); + provider.hostConnected(); + + const element = document.createElement('div'); + document.body.appendChild(element); + + const localConsumer = new UmbContextConsumer(element, testContextAliasAndNotExstingApiAlias, (_instance) => { + expect(false).to.be.true; + }); + localConsumer.hostConnected(); + + // Delayed check to make sure the callback is not called. + Promise.resolve().then(() => { + localConsumer.hostDisconnected(); + provider.hostDisconnected(); + done(); + }); + }); + }); + + describe('Implementation with discriminator method', () => { + type A = { prop: string }; + + function discriminator(instance: unknown): instance is A { + return typeof (instance as any).prop === 'string'; + } + + function badDiscriminator(instance: unknown): instance is A { + return typeof (instance as any).notExistingProp === 'string'; + } + + it('discriminator determines the instance type', async () => { + const localConsumer = new UmbContextConsumer( + document.body, + new UmbContextToken(testContextAlias, undefined, discriminator), + (instance: A) => { + console.log(instance); + }, + ); + localConsumer.hostConnected(); + + // This bit of code is not really a test but it serves as a TypeScript type test, making sure the given type is matches the one given from the Discriminator method. + type TestType = Exclude extends A ? true : never; + const test: TestType = true; + expect(test).to.be.true; + + localConsumer.destroy(); + }); + + it('approving discriminator still fires callback', (done) => { + const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass()); + provider.hostConnected(); + + const element = document.createElement('div'); + document.body.appendChild(element); + + const localConsumer = new UmbContextConsumer( + element, + new UmbContextToken(testContextAlias, undefined, discriminator), + (_instance) => { expect(_instance.prop).to.eq('value from provider'); done(); localConsumer.hostDisconnected(); provider.hostDisconnected(); - } - }, - ); - localConsumer.hostConnected(); - }); + }, + ); + localConsumer.hostConnected(); + }); - /* - Unprovided feature is out commented currently. I'm not sure there is a use case. So lets leave the code around until we know for sure. - it('acts to Context API disconnected', (done) => { - const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass()); - provider.hostConnected(); + it('disapproving discriminator does not fire callback', (done) => { + const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass()); + provider.hostConnected(); - const element = document.createElement('div'); - document.body.appendChild(element); + const element = document.createElement('div'); + document.body.appendChild(element); - let callbackNum = 0; + const localConsumer = new UmbContextConsumer( + element, + new UmbContextToken(testContextAlias, undefined, badDiscriminator), + (_instance) => { + expect(_instance.prop).to.eq('this must not happen!'); + }, + ); + localConsumer.hostConnected(); - const localConsumer = new UmbContextConsumer( - element, - testContextAlias, - (_instance: UmbTestContextConsumerClass | undefined) => { - callbackNum++; - if (callbackNum === 1) { - expect(_instance?.prop).to.eq('value from provider'); - // unregister. - provider.hostDisconnected(); - } else if (callbackNum === 2) { - expect(_instance?.prop).to.be.undefined; - done(); - } - } - ); - localConsumer.hostConnected(); - }); - */ -}); - -describe('UmbContextConsumer with discriminator test', () => { - type A = { prop: string }; - - function discriminator(instance: unknown): instance is A { - return typeof (instance as any).prop === 'string'; - } - - function badDiscriminator(instance: unknown): instance is A { - return typeof (instance as any).notExistingProp === 'string'; - } - - it('discriminator determines the instance type', async () => { - const localConsumer = new UmbContextConsumer( - document.body, - new UmbContextToken(testContextAlias, undefined, discriminator), - (instance: A) => { - console.log(instance); - }, - ); - localConsumer.hostConnected(); - - // This bit of code is not really a test but it serves as a TypeScript type test, making sure the given type is matches the one given from the Discriminator method. - type TestType = Exclude extends A ? true : never; - const test: TestType = true; - expect(test).to.be.true; - - localConsumer.destroy(); - }); - - it('approving discriminator still fires callback', (done) => { - const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass()); - provider.hostConnected(); - - const element = document.createElement('div'); - document.body.appendChild(element); - - const localConsumer = new UmbContextConsumer( - element, - new UmbContextToken(testContextAlias, undefined, discriminator), - (_instance) => { - expect(_instance.prop).to.eq('value from provider'); + Promise.resolve().then(() => { done(); localConsumer.hostDisconnected(); provider.hostDisconnected(); - }, - ); - localConsumer.hostConnected(); - }); - - it('disapproving discriminator does not fire callback', (done) => { - const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass()); - provider.hostConnected(); - - const element = document.createElement('div'); - document.body.appendChild(element); - - const localConsumer = new UmbContextConsumer( - element, - new UmbContextToken(testContextAlias, undefined, badDiscriminator), - (_instance) => { - expect(_instance.prop).to.eq('this must not happen!'); - }, - ); - localConsumer.hostConnected(); - - Promise.resolve().then(() => { - done(); - localConsumer.hostDisconnected(); - provider.hostDisconnected(); + }); }); }); }); From 58f4b18786132d0822576abd67dfdff674aa6704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 21 Nov 2023 20:58:37 +0100 Subject: [PATCH 20/25] update to use the right token --- .../workspace/document-type-workspace-editor.element.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace-editor.element.ts index 592fdfee6e..79469d08b8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/document-type-workspace-editor.element.ts @@ -1,4 +1,4 @@ -import { UmbDocumentTypeWorkspaceContext } from './document-type-workspace.context.js'; +import { UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT } from './document-type-workspace.context.js'; import { UUIInputElement, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; import { css, html, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; @@ -7,7 +7,6 @@ import { UMB_MODAL_MANAGER_CONTEXT_TOKEN, UMB_ICON_PICKER_MODAL, } from '@umbraco-cms/backoffice/modal'; -import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; import { generateAlias } from '@umbraco-cms/backoffice/utils'; @customElement('umb-document-type-workspace-editor') export class UmbDocumentTypeWorkspaceEditorElement extends UmbLitElement { @@ -27,15 +26,15 @@ export class UmbDocumentTypeWorkspaceEditorElement extends UmbLitElement { private _iconColorAlias?: string; // TODO: Color should be using an alias, and look up in some dictionary/key/value) of project-colors. - #workspaceContext?: UmbDocumentTypeWorkspaceContext; + #workspaceContext?: typeof UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT.TYPE; private _modalContext?: UmbModalManagerContext; constructor() { super(); - this.consumeContext(UMB_WORKSPACE_CONTEXT, (instance) => { - this.#workspaceContext = instance as UmbDocumentTypeWorkspaceContext; + this.consumeContext(UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT, (instance) => { + this.#workspaceContext = instance; this.#observeDocumentType(); }); From 3470be65ee2cae824a74ee33c3715f60e33ecc9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 21 Nov 2023 20:58:47 +0100 Subject: [PATCH 21/25] make sure we extend UmbApi --- .../workspace-context/workspace-context.interface.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-context.interface.ts index cdb9fa4b4e..e065c1adb0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-context.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-context/workspace-context.interface.ts @@ -1,4 +1,6 @@ -export interface UmbWorkspaceContextInterface { +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; + +export interface UmbWorkspaceContextInterface extends UmbApi { readonly workspaceAlias: string; // TODO: should we consider another name than entity type. File system files are not entities but still have this type. getEntityType(): string; From 41f1e8f3c2cdf7f43d9fc5fbf9731c02a067470e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 21 Nov 2023 20:59:02 +0100 Subject: [PATCH 22/25] media workspace get api through manifest --- .../media/media/workspace/manifests.ts | 3 +- .../workspace/media-workspace.context.ts | 4 +- .../workspace/media-workspace.element.ts | 63 ++++++++++++++----- 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/manifests.ts index e0c4ae4b7c..d36fa3c4dd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/manifests.ts @@ -11,7 +11,8 @@ const workspace: ManifestWorkspace = { type: 'workspace', alias: 'Umb.Workspace.Media', name: 'Media Workspace', - js: () => import('./media-workspace.element.js'), + element: () => import('./media-workspace.element.js'), + api: () => import('./media-workspace.context.js'), meta: { entityType: 'media', }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts index 65db834f3b..1b0575f358 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.context.ts @@ -7,11 +7,12 @@ import { import { appendToFrozenArray, UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import { UmbApi } from '@umbraco-cms/backoffice/extension-api'; type EntityType = UmbMediaDetailModel; export class UmbMediaWorkspaceContext extends UmbEditableWorkspaceContextBase - implements UmbSaveableWorkspaceContextInterface + implements UmbSaveableWorkspaceContextInterface, UmbApi { #data = new UmbObjectState(undefined); data = this.#data.asObservable(); @@ -85,6 +86,7 @@ export class UmbMediaWorkspaceContext this.#data.destroy(); } } +export const api = UmbMediaWorkspaceContext; export const UMB_MEDIA_WORKSPACE_CONTEXT = new UmbContextToken< UmbSaveableWorkspaceContextInterface, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.element.ts index aeeb6b3531..10c2fc5ecc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/workspace/media-workspace.element.ts @@ -1,28 +1,59 @@ -import { UmbMediaWorkspaceContext } from './media-workspace.context.js'; -import { UmbMediaWorkspaceEditorElement } from './media-workspace-editor.element.js'; -import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; +import { type UmbMediaWorkspaceContext } from './media-workspace.context.js'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import type { UmbRoute } from '@umbraco-cms/backoffice/router'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { type UmbApi, createExtensionApi, UmbExtensionsApiInitializer } from '@umbraco-cms/backoffice/extension-api'; +import { umbExtensionsRegistry, type ManifestWorkspace } from '@umbraco-cms/backoffice/extension-registry'; +import { UmbWorkspaceIsNewRedirectController } from '@umbraco-cms/backoffice/workspace'; @customElement('umb-media-workspace') export class UmbMediaWorkspaceElement extends UmbLitElement { - public readonly workspaceAlias = 'Umb.Workspace.Media'; - - #workspaceContext = new UmbMediaWorkspaceContext(this); - #element = new UmbMediaWorkspaceEditorElement(); + #workspaceContext?: UmbMediaWorkspaceContext; @state() - _routes: UmbRoute[] = [ - { - path: 'edit/:id', - component: () => this.#element, - setup: (_component, info) => { - const id = info.match.params.id; - this.#workspaceContext.load(id); + _routes: UmbRoute[] = []; + + public set manifest(manifest: ManifestWorkspace) { + createExtensionApi(manifest, [this]).then((context) => { + if (context) { + this.#gotWorkspaceContext(context); + } + }); + } + + #gotWorkspaceContext(context: UmbApi) { + this.#workspaceContext = context as UmbMediaWorkspaceContext; + + this._routes = [ + { + path: 'create/:parentId', // /:mediaTypeKey + component: import('./media-workspace-editor.element.js'), + setup: async (_component, info) => { + // TODO: Remember the perspective of permissions here, we need to check if the user has access to create a document of this type under this parent? + const parentId = info.match.params.parentId === 'null' ? null : info.match.params.parentId; + //const mediaTypeKey = info.match.params.mediaTypeKey; + this.#workspaceContext!.create(parentId /** , mediaTypeKey */); + + new UmbWorkspaceIsNewRedirectController( + this, + this.#workspaceContext!, + this.shadowRoot!.querySelector('umb-router-slot')!, + ); + }, }, - }, - ]; + { + path: 'edit/:id', + component: import('./media-workspace-editor.element.js'), + setup: (_component, info) => { + const id = info.match.params.id; + this.#workspaceContext!.load(id); + }, + }, + ]; + + new UmbExtensionsApiInitializer(this, umbExtensionsRegistry, 'workspaceContext', [this, this.#workspaceContext]); + } render() { return html``; From 4f63dc5856efa8e8231c196a31e7ff7c1df9f3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 21 Nov 2023 21:01:54 +0100 Subject: [PATCH 23/25] clean up --- .../templates/workspace/template-workspace.element.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/template-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/template-workspace.element.ts index c73caadf91..ca9068383a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/template-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/workspace/template-workspace.element.ts @@ -1,6 +1,6 @@ import { UmbTemplateWorkspaceContext } from './template-workspace.context.js'; import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; -import type { IRoutingInfo, PageComponent, UmbRoute, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router'; +import type { IRoutingInfo, PageComponent, UmbRoute } from '@umbraco-cms/backoffice/router'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import '../../components/insert-menu/templating-insert-menu.element.js'; @@ -9,10 +9,6 @@ import { UmbWorkspaceIsNewRedirectController } from '@umbraco-cms/backoffice/wor @customElement('umb-template-workspace') export class UmbTemplateWorkspaceElement extends UmbLitElement { - public load(entityId: string) { - this.#templateWorkspaceContext.load(entityId); - } - #templateWorkspaceContext = new UmbTemplateWorkspaceContext(this); #element = document.createElement('umb-template-workspace-editor'); @@ -29,7 +25,7 @@ export class UmbTemplateWorkspaceElement extends UmbLitElement { new UmbWorkspaceIsNewRedirectController( this, this.#templateWorkspaceContext, - this.shadowRoot!.querySelector('umb-router-slot')! + this.shadowRoot!.querySelector('umb-router-slot')!, ); }, }, From 57eec9f7f96df687929819c493873737ef5fe483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 21 Nov 2023 21:21:49 +0100 Subject: [PATCH 24/25] other minor corrections --- .../workspace/data-type-workspace.context.ts | 18 ++++++++--------- .../workspace/dictionary-workspace.context.ts | 10 +++++----- .../workspace/media-type-workspace.context.ts | 16 +++++++-------- .../member-group-workspace.context.ts | 4 ++-- .../member-type-workspace.context.ts | 4 ++-- .../workspace/member-workspace.context.ts | 2 +- .../language/language-workspace.context.ts | 8 ++++---- .../relation-type-workspace.context.ts | 10 +++++----- .../partial-view-workspace.context.ts | 16 +++++++-------- .../workspace/stylesheet-workspace.context.ts | 20 +++++++++---------- .../workspace/stylesheet-workspace.element.ts | 6 +++--- .../src/packages/templating/utils.ts | 10 ++-------- .../src/shared/utils/diff.type.ts | 4 ++-- .../src/shared/utils/index.ts | 4 +++- .../src/shared/utils/path-decode.function.ts | 1 + .../src/shared/utils/path-encode.function.ts | 1 + .../src/shared/utils/umbraco-path.function.ts | 1 + 17 files changed, 67 insertions(+), 68 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/shared/utils/path-decode.function.ts create mode 100644 src/Umbraco.Web.UI.Client/src/shared/utils/path-encode.function.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.context.ts index 21a2f7af84..d20a5bffa0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.context.ts @@ -28,17 +28,17 @@ export class UmbDataTypeWorkspaceContext { // TODO: revisit. temp solution because the create and response models are different. #data = new UmbObjectState(undefined); - data = this.#data.asObservable(); + readonly data = this.#data.asObservable(); #getDataPromise?: Promise; - name = this.#data.asObservablePart((data) => data?.name); - id = this.#data.asObservablePart((data) => data?.id); + readonly name = this.#data.asObservablePart((data) => data?.name); + readonly id = this.#data.asObservablePart((data) => data?.id); - propertyEditorUiAlias = this.#data.asObservablePart((data) => data?.propertyEditorUiAlias); - propertyEditorSchemaAlias = this.#data.asObservablePart((data) => data?.propertyEditorAlias); + readonly propertyEditorUiAlias = this.#data.asObservablePart((data) => data?.propertyEditorUiAlias); + readonly propertyEditorSchemaAlias = this.#data.asObservablePart((data) => data?.propertyEditorAlias); #properties = new UmbObjectState | undefined>(undefined); - properties: Observable | undefined> = this.#properties.asObservable(); + readonly properties: Observable | undefined> = this.#properties.asObservable(); private _propertyEditorSchemaConfigDefaultData: Array = []; private _propertyEditorUISettingsDefaultData: Array = []; @@ -53,13 +53,13 @@ export class UmbDataTypeWorkspaceContext private _propertyEditorUISettingsSchemaAlias?: string; #defaults = new UmbArrayState([], (entry) => entry.alias); - defaults = this.#defaults.asObservable(); + readonly defaults = this.#defaults.asObservable(); #propertyEditorUiIcon = new UmbStringState(null); - propertyEditorUiIcon = this.#propertyEditorUiIcon.asObservable(); + readonly propertyEditorUiIcon = this.#propertyEditorUiIcon.asObservable(); #propertyEditorUiName = new UmbStringState(null); - propertyEditorUiName = this.#propertyEditorUiName.asObservable(); + readonly propertyEditorUiName = this.#propertyEditorUiName.asObservable(); constructor(host: UmbControllerHostElement) { super(host, 'Umb.Workspace.DataType', new UmbDataTypeDetailRepository(host)); diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/workspace/dictionary-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/workspace/dictionary-workspace.context.ts index 997db59a7d..b27f8bae0b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/workspace/dictionary-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/workspace/dictionary-workspace.context.ts @@ -1,9 +1,9 @@ import { UmbDictionaryRepository } from '../repository/dictionary.repository.js'; import { - UmbSaveableWorkspaceContextInterface, + type UmbSaveableWorkspaceContextInterface, UmbEditableWorkspaceContextBase, } from '@umbraco-cms/backoffice/workspace'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { DictionaryItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; @@ -13,10 +13,10 @@ export class UmbDictionaryWorkspaceContext implements UmbSaveableWorkspaceContextInterface { #data = new UmbObjectState(undefined); - data = this.#data.asObservable(); + readonly data = this.#data.asObservable(); - name = this.#data.asObservablePart((data) => data?.name); - dictionary = this.#data.asObservablePart((data) => data); + readonly name = this.#data.asObservablePart((data) => data?.name); + readonly dictionary = this.#data.asObservablePart((data) => data); constructor(host: UmbControllerHostElement) { super(host, 'Umb.Workspace.Dictionary', new UmbDictionaryRepository(host)); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts index e8ab17ba2c..1127403282 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/media-type-workspace.context.ts @@ -1,9 +1,9 @@ import { UmbMediaTypeRepository } from '../repository/media-type.repository.js'; import { - UmbSaveableWorkspaceContextInterface, + type UmbSaveableWorkspaceContextInterface, UmbEditableWorkspaceContextBase, } from '@umbraco-cms/backoffice/workspace'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; import { MediaTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; @@ -14,14 +14,14 @@ export class UmbMediaTypeWorkspaceContext implements UmbSaveableWorkspaceContextInterface { #data = new UmbObjectState(undefined); - data = this.#data.asObservable(); + readonly data = this.#data.asObservable(); #getDataPromise?: Promise; - name = this.#data.asObservablePart((data) => data?.name); - id = this.#data.asObservablePart((data) => data?.id); - alias = this.#data.asObservablePart((data) => data?.alias); - description = this.#data.asObservablePart((data) => data?.description); - icon = this.#data.asObservablePart((data) => data?.icon); + readonly name = this.#data.asObservablePart((data) => data?.name); + readonly id = this.#data.asObservablePart((data) => data?.id); + readonly alias = this.#data.asObservablePart((data) => data?.alias); + readonly description = this.#data.asObservablePart((data) => data?.description); + readonly icon = this.#data.asObservablePart((data) => data?.icon); constructor(host: UmbControllerHostElement) { super(host, 'Umb.Workspace.MediaType', new UmbMediaTypeRepository(host)); diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-groups/workspace/member-group-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-groups/workspace/member-group-workspace.context.ts index 38da5949c0..4af1a59466 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-groups/workspace/member-group-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-groups/workspace/member-group-workspace.context.ts @@ -3,10 +3,10 @@ import type { UmbMemberGroupDetailModel } from '../types.js'; import { UMB_MEMBER_GROUP_ENTITY_TYPE } from '../entity.js'; import { UMB_MEMBER_GROUP_WORKSPACE_ALIAS } from './manifests.js'; import { - UmbSaveableWorkspaceContextInterface, + type UmbSaveableWorkspaceContextInterface, UmbEditableWorkspaceContextBase, } from '@umbraco-cms/backoffice/workspace'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; export class UmbMemberGroupWorkspaceContext diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/workspace/member-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/workspace/member-type-workspace.context.ts index 1ce66f3df7..ee32cbdaf9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/workspace/member-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/workspace/member-type-workspace.context.ts @@ -1,10 +1,10 @@ import { UmbMemberTypeRepository } from '../repository/member-type.repository.js'; import { - UmbSaveableWorkspaceContextInterface, + type UmbSaveableWorkspaceContextInterface, UmbEditableWorkspaceContextBase, } from '@umbraco-cms/backoffice/workspace'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; // TODO => use correct tpye diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/members/workspace/member-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/members/workspace/member-workspace.context.ts index ad6c12f22c..55ecf02ee2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/members/workspace/member-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/members/workspace/member-workspace.context.ts @@ -3,7 +3,7 @@ import type { UmbMemberDetailModel } from '../types.js'; import { UMB_MEMBER_ENTITY_TYPE } from '../entity.js'; import { UMB_MEMBER_WORKSPACE_ALIAS } from './manifests.js'; import { - UmbSaveableWorkspaceContextInterface, + type UmbSaveableWorkspaceContextInterface, UmbEditableWorkspaceContextBase, } from '@umbraco-cms/backoffice/workspace'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.context.ts index b0f89da15f..e0fd57e2bc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/languages/workspace/language/language-workspace.context.ts @@ -1,9 +1,9 @@ import { UmbLanguageRepository } from '../../repository/language.repository.js'; import { - UmbSaveableWorkspaceContextInterface, + type UmbSaveableWorkspaceContextInterface, UmbEditableWorkspaceContextBase, } from '@umbraco-cms/backoffice/workspace'; -import { ApiError, LanguageResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { ApiError, type LanguageResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; @@ -13,11 +13,11 @@ export class UmbLanguageWorkspaceContext implements UmbSaveableWorkspaceContextInterface { #data = new UmbObjectState(undefined); - data = this.#data.asObservable(); + readonly data = this.#data.asObservable(); // TODO: this is a temp solution to bubble validation errors to the UI #validationErrors = new UmbObjectState(undefined); - validationErrors = this.#validationErrors.asObservable(); + readonly validationErrors = this.#validationErrors.asObservable(); constructor(host: UmbControllerHostElement) { super(host, 'Umb.Workspace.Language', new UmbLanguageRepository(host)); diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/relation-types/workspace/relation-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/relation-types/workspace/relation-type-workspace.context.ts index d4be3a51e0..b28d8568d8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/relation-types/workspace/relation-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/relation-types/workspace/relation-type-workspace.context.ts @@ -1,11 +1,11 @@ import { UmbRelationTypeRepository } from '../repository/relation-type.repository.js'; import { - UmbSaveableWorkspaceContextInterface, + type UmbSaveableWorkspaceContextInterface, UmbEditableWorkspaceContextBase, } from '@umbraco-cms/backoffice/workspace'; import type { RelationTypeBaseModel, RelationTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; export class UmbRelationTypeWorkspaceContext @@ -13,9 +13,9 @@ export class UmbRelationTypeWorkspaceContext implements UmbSaveableWorkspaceContextInterface { #data = new UmbObjectState(undefined); - data = this.#data.asObservable(); - name = this.#data.asObservablePart((data) => data?.name); - id = this.#data.asObservablePart((data) => data?.id); + readonly data = this.#data.asObservable(); + readonly name = this.#data.asObservablePart((data) => data?.name); + readonly id = this.#data.asObservablePart((data) => data?.id); constructor(host: UmbControllerHostElement) { super(host, 'Umb.Workspace.RelationType', new UmbRelationTypeRepository(host)); diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/partial-view-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/partial-view-workspace.context.ts index 6b818a1702..7ea5a2601b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/partial-view-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/workspace/partial-view-workspace.context.ts @@ -1,14 +1,14 @@ import { UmbPartialViewRepository } from '../repository/partial-view.repository.js'; -import { UmbPartialViewDetailModel } from '../types.js'; +import type { UmbPartialViewDetailModel } from '../types.js'; import { UMB_PARTIAL_VIEW_ENTITY_TYPE } from '../entity.js'; import { UmbBooleanState, UmbDeepState } from '@umbraco-cms/backoffice/observable-api'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbSaveableWorkspaceContextInterface, UmbEditableWorkspaceContextBase, } from '@umbraco-cms/backoffice/workspace'; import { loadCodeEditor } from '@umbraco-cms/backoffice/code-editor'; -import { UpdatePartialViewRequestModel } from '@umbraco-cms/backoffice/backend-api'; +import type { UpdatePartialViewRequestModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; export class UmbPartialViewWorkspaceContext @@ -47,13 +47,13 @@ export class UmbPartialViewWorkspaceContext } #data = new UmbDeepState(undefined); - data = this.#data.asObservable(); - name = this.#data.asObservablePart((data) => data?.name); - content = this.#data.asObservablePart((data) => data?.content); - path = this.#data.asObservablePart((data) => data?.path); + readonly data = this.#data.asObservable(); + readonly name = this.#data.asObservablePart((data) => data?.name); + readonly content = this.#data.asObservablePart((data) => data?.content); + readonly path = this.#data.asObservablePart((data) => data?.path); #isCodeEditorReady = new UmbBooleanState(false); - isCodeEditorReady = this.#isCodeEditorReady.asObservable(); + readonly isCodeEditorReady = this.#isCodeEditorReady.asObservable(); constructor(host: UmbControllerHostElement) { super(host, 'Umb.Workspace.PartialView', new UmbPartialViewRepository(host)); diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts index 512d396a11..5d0aed22ca 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.context.ts @@ -1,13 +1,13 @@ import { UmbStylesheetRepository } from '../repository/stylesheet.repository.js'; -import { StylesheetDetails } from '../index.js'; +import type { StylesheetDetails } from '../index.js'; import { - UmbSaveableWorkspaceContextInterface, + type UmbSaveableWorkspaceContextInterface, UmbEditableWorkspaceContextBase, } from '@umbraco-cms/backoffice/workspace'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbArrayState, UmbBooleanState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { loadCodeEditor } from '@umbraco-cms/backoffice/code-editor'; -import { RichTextRuleModel, UpdateStylesheetRequestModel } from '@umbraco-cms/backoffice/backend-api'; +import type { RichTextRuleModel, UpdateStylesheetRequestModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; export type RichTextRuleModelSortable = RichTextRuleModel & { sortOrder?: number }; @@ -18,14 +18,14 @@ export class UmbStylesheetWorkspaceContext { #data = new UmbObjectState(undefined); #rules = new UmbArrayState([], (rule) => rule.name); - data = this.#data.asObservable(); - rules = this.#rules.asObservable(); - name = this.#data.asObservablePart((data) => data?.name); - content = this.#data.asObservablePart((data) => data?.content); - path = this.#data.asObservablePart((data) => data?.path); + readonly data = this.#data.asObservable(); + readonly rules = this.#rules.asObservable(); + readonly name = this.#data.asObservablePart((data) => data?.name); + readonly content = this.#data.asObservablePart((data) => data?.content); + readonly path = this.#data.asObservablePart((data) => data?.path); #isCodeEditorReady = new UmbBooleanState(false); - isCodeEditorReady = this.#isCodeEditorReady.asObservable(); + readonly isCodeEditorReady = this.#isCodeEditorReady.asObservable(); constructor(host: UmbControllerHostElement) { super(host, 'Umb.Workspace.StyleSheet', new UmbStylesheetRepository(host)); diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.element.ts index c4f74d092e..1506091374 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/stylesheet-workspace.element.ts @@ -1,10 +1,10 @@ -import { serverFilePathFromUrlFriendlyPath } from '../../utils.js'; import { UmbStylesheetWorkspaceContext } from './stylesheet-workspace.context.js'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import type { UmbRoute } from '@umbraco-cms/backoffice/router'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbWorkspaceIsNewRedirectController } from '@umbraco-cms/backoffice/workspace'; +import { decodeFilePath } from '@umbraco-cms/backoffice/utils'; @customElement('umb-stylesheet-workspace') export class UmbStylesheetWorkspaceElement extends UmbLitElement { @@ -17,7 +17,7 @@ export class UmbStylesheetWorkspaceElement extends UmbLitElement { component: import('./stylesheet-workspace-editor.element.js'), setup: async (_component, info) => { const path = info.match.params.path === 'null' ? null : info.match.params.path; - const serverPath = path === null ? null : serverFilePathFromUrlFriendlyPath(path); + const serverPath = path === null ? null : decodeFilePath(path); await this.#workspaceContext.create(serverPath); await this.#workspaceContext.setRules([]); @@ -34,7 +34,7 @@ export class UmbStylesheetWorkspaceElement extends UmbLitElement { setup: (_component, info) => { this.removeControllerByAlias('_observeIsNew'); const path = info.match.params.path; - const serverPath = serverFilePathFromUrlFriendlyPath(path); + const serverPath = decodeFilePath(path); this.#workspaceContext.load(serverPath); }, }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/utils.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/utils.ts index cb7253772a..57c84ea4bf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/utils.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/utils.ts @@ -1,9 +1,3 @@ -// TODO: we can try and make pretty urls if we want to -export const urlFriendlyPathFromServerFilePath = (path: string) => encodeURIComponent(path).replace('.', '-'); - -// TODO: we can try and make pretty urls if we want to -export const serverFilePathFromUrlFriendlyPath = (unique: string) => decodeURIComponent(unique.replace('-', '.')); - //Below are a copy of export const getInsertDictionarySnippet = (nodeName: string) => { return `@Umbraco.GetDictionaryValue("${nodeName}")`; @@ -31,10 +25,10 @@ export const getRenderBodySnippet = () => '@RenderBody()'; export const getRenderSectionSnippet = (sectionName: string, isMandatory: boolean) => `@RenderSection("${sectionName}", ${isMandatory})`; -export const getAddSectionSnippet = (sectionName: string) => `@section ${sectionName} +export const getAddSectionSnippet = (sectionName: string) => `@section ${sectionName} { - + }`; diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/diff.type.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/diff.type.ts index cbce25da17..699ea2524f 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/diff.type.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/diff.type.ts @@ -1,5 +1,5 @@ -type FilterKeys = { +type _FilterKeys = { [K in keyof T]: K extends keyof U ? never : K; }; -export type Diff = Pick[keyof T]>; +export type Diff = Pick[keyof T]>; diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/index.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/index.ts index c5d439c2e2..7b605bb5de 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/index.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/index.ts @@ -4,11 +4,13 @@ export * from './ensure-path-ends-with-slash.function.js'; export * from './generate-umbraco-alias.function.js'; export * from './increment-string.function.js'; export * from './media-helper.service.js'; +export * from './pagination-manager/pagination.manager.js'; +export * from './path-decode.function.js'; +export * from './path-encode.function.js'; export * from './path-folder-name.function.js'; export * from './selection-manager.js'; export * from './udi-service.js'; export * from './umbraco-path.function.js'; -export * from './pagination-manager/pagination.manager.js'; declare global { interface Window { diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/path-decode.function.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/path-decode.function.ts new file mode 100644 index 0000000000..4858a64a5d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/path-decode.function.ts @@ -0,0 +1 @@ +export const decodeFilePath = (unique: string) => decodeURIComponent(unique.replace('-', '.')); diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/path-encode.function.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/path-encode.function.ts new file mode 100644 index 0000000000..ef3e8b0a2b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/path-encode.function.ts @@ -0,0 +1 @@ +export const encodeFilePath = (path: string) => encodeURIComponent(path).replace('.', '-'); diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/umbraco-path.function.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/umbraco-path.function.ts index b536ed8a2e..9c54a56c4e 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/umbraco-path.function.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/umbraco-path.function.ts @@ -1,3 +1,4 @@ +// TODO: Rename to something more obvious, naming wise this can mean anything. I suggest: umbracoManagementApiPath() export function umbracoPath(path: string) { return `/umbraco/management/api/v1${path}`; } From 0492287ecac47142232b7c45101d4f36c353fcfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 21 Nov 2023 21:28:18 +0100 Subject: [PATCH 25/25] remove _instance --- .../src/libs/context-api/consume/context-consumer.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.test.ts b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.test.ts index 80b1df93b1..aee8547152 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/context-api/consume/context-consumer.test.ts @@ -135,7 +135,7 @@ describe('UmbContextConsumer', () => { const element = document.createElement('div'); document.body.appendChild(element); - const localConsumer = new UmbContextConsumer(element, testContextAliasAndNotExstingApiAlias, (_instance) => { + const localConsumer = new UmbContextConsumer(element, testContextAliasAndNotExstingApiAlias, () => { expect(false).to.be.true; }); localConsumer.hostConnected();