From 7e6b14df2fe9e408a93dd77fe049195efa52abef Mon Sep 17 00:00:00 2001 From: JesmoDev <26099018+JesmoDev@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:36:51 +0200 Subject: [PATCH 001/120] init workspace setup --- .../packages/webhook/menu-item/manifests.ts | 2 +- .../webhook/repository/detail/index.ts | 2 + .../webhook/repository/detail/manifests.ts | 21 +++ .../detail/webhook-detail.repository.ts | 17 ++ .../webhook-detail.server.data-source.ts | 162 ++++++++++++++++++ .../repository/detail/webhook-detail.store.ts | 25 +++ .../src/packages/webhook/repository/index.ts | 2 + .../packages/webhook/repository/item/index.ts | 3 + .../webhook/repository/item/manifests.ts | 20 +++ .../packages/webhook/repository/item/types.ts | 4 + .../item/webhook-item.repository.ts | 12 ++ .../item/webhook-item.server.data-source.ts | 38 ++++ .../repository/item/webhook-item.store.ts | 26 +++ .../packages/webhook/repository/manifests.ts | 5 + .../packages/webhook/workspace/manifests.ts | 3 +- .../workspace/webhook-root/manifests.ts | 13 ++ .../webhook-root-workspace.element.ts} | 10 +- .../webhook/workspace/webhook/manifests.ts | 26 +-- .../webhook-details-workspace-view.element.ts | 22 +++ .../webhook-workspace-editor.element.ts | 89 ++++++++++ .../webhook-workspace.context-token.ts | 12 ++ .../webhook/webhook-workspace.context.ts | 128 ++++++++++++-- .../webhook/webhook-workspace.element.ts | 20 --- 23 files changed, 598 insertions(+), 64 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/webhook-detail.repository.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/webhook-detail.server.data-source.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/webhook-detail.store.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/webhook/repository/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/types.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/webhook-item.repository.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/webhook-item.server.data-source.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/webhook-item.store.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/webhook/repository/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook-root/manifests.ts rename src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/{views/overview/webhook-overview-view.element.ts => webhook-root/webhook-root-workspace.element.ts} (50%) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/views/webhook-details-workspace-view.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace-editor.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace.context-token.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/menu-item/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/menu-item/manifests.ts index 5b5064bb88..f9613810e4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/menu-item/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/menu-item/manifests.ts @@ -9,7 +9,7 @@ const menuItem: ManifestMenuItem = { meta: { label: 'Webhooks', icon: 'icon-webhook', - entityType: UMB_WEBHOOK_ENTITY_TYPE, + entityType: 'webhook-root', menus: ['Umb.Menu.AdvancedSettings'], }, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/index.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/index.ts new file mode 100644 index 0000000000..05c54b0307 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/index.ts @@ -0,0 +1,2 @@ +export { UmbWebhookDetailRepository } from './webhook-detail.repository.js'; +export { UMB_WEBHOOK_DETAIL_REPOSITORY_ALIAS } from './manifests.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/manifests.ts new file mode 100644 index 0000000000..9f23de3be5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/manifests.ts @@ -0,0 +1,21 @@ +import type { ManifestRepository, ManifestStore, ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const UMB_WEBHOOK_DETAIL_REPOSITORY_ALIAS = 'Umb.Repository.Webhook.Detail'; + +const repository: ManifestRepository = { + type: 'repository', + alias: UMB_WEBHOOK_DETAIL_REPOSITORY_ALIAS, + name: 'Webhook Detail Repository', + api: () => import('./webhook-detail.repository.js'), +}; + +export const UMB_WEBHOOK_DETAIL_STORE_ALIAS = 'Umb.Store.Webhook.Detail'; + +const store: ManifestStore = { + type: 'store', + alias: UMB_WEBHOOK_DETAIL_STORE_ALIAS, + name: 'Webhook Detail Store', + api: () => import('./webhook-detail.store.js'), +}; + +export const manifests: Array = [repository, store]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/webhook-detail.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/webhook-detail.repository.ts new file mode 100644 index 0000000000..d89115844d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/webhook-detail.repository.ts @@ -0,0 +1,17 @@ +import type { UmbWebhookDetailModel } from '../../types.js'; +import { UmbWebhookServerDataSource } from './webhook-detail.server.data-source.js'; +import { UMB_WEBHOOK_DETAIL_STORE_CONTEXT } from './webhook-detail.store.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbDetailRepositoryBase } from '@umbraco-cms/backoffice/repository'; + +export class UmbWebhookDetailRepository extends UmbDetailRepositoryBase { + constructor(host: UmbControllerHost) { + super(host, UmbWebhookServerDataSource, UMB_WEBHOOK_DETAIL_STORE_CONTEXT); + } + + async create(model: UmbWebhookDetailModel) { + return super.create(model, null); + } +} + +export default UmbWebhookDetailRepository; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/webhook-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/webhook-detail.server.data-source.ts new file mode 100644 index 0000000000..8ae750bd4d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/webhook-detail.server.data-source.ts @@ -0,0 +1,162 @@ +import type { UmbWebhookDetailModel } from '../../types.js'; +import { UMB_WEBHOOK_ENTITY_TYPE } from '../../entity.js'; +import { UmbId } from '@umbraco-cms/backoffice/id'; +import type { UmbDetailDataSource } from '@umbraco-cms/backoffice/repository'; +import type { + CreateWebhookRequestModel, + UpdateWebhookRequestModel, +} from '@umbraco-cms/backoffice/external/backend-api'; +import { WebhookService } from '@umbraco-cms/backoffice/external/backend-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +/** + * A data source for the Webhook that fetches data from the server + * @export + * @class UmbWebhookServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbWebhookServerDataSource implements UmbDetailDataSource { + #host: UmbControllerHost; + + /** + * Creates an instance of UmbWebhookServerDataSource. + * @param {UmbControllerHost} host + * @memberof UmbWebhookServerDataSource + */ + constructor(host: UmbControllerHost) { + this.#host = host; + } + + /** + * Creates a new Webhook scaffold + * @param {Partial} [preset] + * @return { CreateWebhookRequestModel } + * @memberof UmbWebhookServerDataSource + */ + async createScaffold(preset: Partial = {}) { + const data: UmbWebhookDetailModel = { + entityType: UMB_WEBHOOK_ENTITY_TYPE, + fallbackIsoCode: null, + isDefault: false, + isMandatory: false, + name: '', + unique: '', + ...preset, + }; + + return { data }; + } + + /** + * Fetches a Webhook with the given id from the server + * @param {string} unique + * @return {*} + * @memberof UmbWebhookServerDataSource + */ + async read(unique: string) { + if (!unique) throw new Error('Unique is missing'); + + const { data, error } = await tryExecuteAndNotify( + this.#host, + WebhookService.getWebhookByIsoCode({ isoCode: unique }), + ); + + if (error || !data) { + return { error }; + } + + // TODO: make data mapper to prevent errors + const dataType: UmbWebhookDetailModel = { + entityType: UMB_WEBHOOK_ENTITY_TYPE, + fallbackIsoCode: data.fallbackIsoCode?.toLowerCase() || null, + isDefault: data.isDefault, + isMandatory: data.isMandatory, + name: data.name, + unique: data.isoCode.toLowerCase(), + }; + + return { data: dataType }; + } + + /** + * Inserts a new Webhook on the server + * @param {UmbWebhookDetailModel} model + * @return {*} + * @memberof UmbWebhookServerDataSource + */ + async create(model: UmbWebhookDetailModel) { + if (!model) throw new Error('Webhook is missing'); + + // TODO: make data mapper to prevent errors + const requestBody: CreateWebhookRequestModel = { + fallbackIsoCode: model.fallbackIsoCode?.toLowerCase(), + isDefault: model.isDefault, + isMandatory: model.isMandatory, + isoCode: model.unique.toLowerCase(), + name: model.name, + }; + + const { data, error } = await tryExecuteAndNotify( + this.#host, + WebhookService.postWebhook({ + requestBody, + }), + ); + + if (data) { + return this.read(data); + } + + return { error }; + } + + /** + * Updates a Webhook on the server + * @param {UmbWebhookDetailModel} Webhook + * @return {*} + * @memberof UmbWebhookServerDataSource + */ + async update(model: UmbWebhookDetailModel) { + if (!model.unique) throw new Error('Unique is missing'); + + // TODO: make data mapper to prevent errors + const requestBody: UpdateWebhookRequestModel = { + fallbackIsoCode: model.fallbackIsoCode?.toLowerCase(), + isDefault: model.isDefault, + isMandatory: model.isMandatory, + name: model.name, + }; + + const { error } = await tryExecuteAndNotify( + this.#host, + WebhookService.putWebhookByIsoCode({ + isoCode: model.unique.toLowerCase(), + requestBody, + }), + ); + + if (!error) { + return this.read(model.unique); + } + + return { error }; + } + + /** + * Deletes a Webhook on the server + * @param {string} unique + * @return {*} + * @memberof UmbWebhookServerDataSource + */ + async delete(unique: string) { + if (!unique) throw new Error('Unique is missing'); + + return tryExecuteAndNotify( + this.#host, + WebhookService.deleteWebhookByIsoCode({ + isoCode: unique, + }), + ); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/webhook-detail.store.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/webhook-detail.store.ts new file mode 100644 index 0000000000..f68a946da7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/detail/webhook-detail.store.ts @@ -0,0 +1,25 @@ +import type { UmbWebhookDetailModel } from '../../types.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import { UmbDetailStoreBase } from '@umbraco-cms/backoffice/store'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +/** + * @export + * @class UmbWebhookDetailStore + * @extends {UmbStoreBase} + * @description - Data Store for Webhook Details + */ +export class UmbWebhookDetailStore extends UmbDetailStoreBase { + /** + * Creates an instance of UmbWebhookDetailStore. + * @param {UmbControllerHost} host + * @memberof UmbWebhookDetailStore + */ + constructor(host: UmbControllerHost) { + super(host, UMB_WEBHOOK_DETAIL_STORE_CONTEXT.toString()); + } +} + +export default UmbWebhookDetailStore; + +export const UMB_WEBHOOK_DETAIL_STORE_CONTEXT = new UmbContextToken('UmbWebhookDetailStore'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/index.ts new file mode 100644 index 0000000000..e94276bc0a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/index.ts @@ -0,0 +1,2 @@ +export * from './detail/index.js'; +export * from './item/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/index.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/index.ts new file mode 100644 index 0000000000..899fdf9312 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/index.ts @@ -0,0 +1,3 @@ +export { UmbWebhookItemRepository } from './webhook-item.repository.js'; +export { UMB_WEBHOOK_ITEM_REPOSITORY_ALIAS } from './manifests.js'; +export type { UmbWebhookItemModel } from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/manifests.ts new file mode 100644 index 0000000000..bce6c4ff40 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/manifests.ts @@ -0,0 +1,20 @@ +import type { ManifestRepository, ManifestItemStore, ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const UMB_WEBHOOK_ITEM_REPOSITORY_ALIAS = 'Umb.Repository.WebhookItem'; +export const UMB_WEBHOOK_STORE_ALIAS = 'Umb.Store.WebhookItem'; + +const itemRepository: ManifestRepository = { + type: 'repository', + alias: UMB_WEBHOOK_ITEM_REPOSITORY_ALIAS, + name: 'Webhook Item Repository', + api: () => import('./webhook-item.repository.js'), +}; + +const itemStore: ManifestItemStore = { + type: 'itemStore', + alias: UMB_WEBHOOK_STORE_ALIAS, + name: 'Webhook Item Store', + api: () => import('./webhook-item.store.js'), +}; + +export const manifests: Array = [itemRepository, itemStore]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/types.ts new file mode 100644 index 0000000000..de42a3a1e5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/types.ts @@ -0,0 +1,4 @@ +export interface UmbWebhookItemModel { + unique: string; + name: string; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/webhook-item.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/webhook-item.repository.ts new file mode 100644 index 0000000000..b4d1f79377 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/webhook-item.repository.ts @@ -0,0 +1,12 @@ +import { UmbWebhookItemServerDataSource } from './webhook-item.server.data-source.js'; +import { UMB_WEBHOOK_ITEM_STORE_CONTEXT } from './webhook-item.store.js'; +import type { UmbWebhookItemModel } from './types.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbItemRepositoryBase } from '@umbraco-cms/backoffice/repository'; + +export class UmbWebhookItemRepository extends UmbItemRepositoryBase { + constructor(host: UmbControllerHost) { + super(host, UmbWebhookItemServerDataSource, UMB_WEBHOOK_ITEM_STORE_CONTEXT); + } +} +export default UmbWebhookItemRepository; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/webhook-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/webhook-item.server.data-source.ts new file mode 100644 index 0000000000..6cccb3a9e4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/webhook-item.server.data-source.ts @@ -0,0 +1,38 @@ +import type { UmbWebhookItemModel } from './types.js'; +import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository'; +import type { WebhookItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; +import { WebhookService } from '@umbraco-cms/backoffice/external/backend-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +/** + * A server data source for Webhook items + * @export + * @class UmbWebhookItemServerDataSource + * @implements {DocumentTreeDataSource} + */ +export class UmbWebhookItemServerDataSource extends UmbItemServerDataSourceBase< + WebhookItemResponseModel, + UmbWebhookItemModel +> { + /** + * Creates an instance of UmbWebhookItemServerDataSource. + * @param {UmbControllerHost} host + * @memberof UmbWebhookItemServerDataSource + */ + constructor(host: UmbControllerHost) { + super(host, { + getItems, + mapper, + }); + } +} + +/* eslint-disable local-rules/no-direct-api-import */ +const getItems = (uniques: Array) => WebhookService.getItemWebhook({ isoCode: uniques }); + +const mapper = (item: WebhookItemResponseModel): UmbWebhookItemModel => { + return { + unique: item.isoCode, + name: item.name, + }; +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/webhook-item.store.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/webhook-item.store.ts new file mode 100644 index 0000000000..8e363de23e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/item/webhook-item.store.ts @@ -0,0 +1,26 @@ +import type { UmbWebhookItemModel } from './types.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbItemStoreBase } from '@umbraco-cms/backoffice/store'; + +/** + * @export + * @class UmbWebhookItemStore + * @extends {UmbStoreBase} + * @description - Data Store for Webhook items + */ + +export class UmbWebhookItemStore extends UmbItemStoreBase { + /** + * Creates an instance of UmbWebhookItemStore. + * @param {UmbControllerHost} host + * @memberof UmbWebhookItemStore + */ + constructor(host: UmbControllerHost) { + super(host, UMB_WEBHOOK_ITEM_STORE_CONTEXT.toString()); + } +} + +export default UmbWebhookItemStore; + +export const UMB_WEBHOOK_ITEM_STORE_CONTEXT = new UmbContextToken('UmbWebhookItemStore'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/manifests.ts new file mode 100644 index 0000000000..37dcb889ef --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/repository/manifests.ts @@ -0,0 +1,5 @@ +import { manifests as detailManifests } from './detail/manifests.js'; +import { manifests as itemManifests } from './item/manifests.js'; +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [...detailManifests, ...itemManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/manifests.ts index 3bfd7ea8e4..b35e2e3aa7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/manifests.ts @@ -1,4 +1,5 @@ import { manifests as webhookManifests } from './webhook/manifests.js'; +import { manifests as webhookRootManifests } from './webhook-root/manifests.js'; import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; -export const manifests: Array = [...webhookManifests]; +export const manifests: Array = [...webhookManifests, ...webhookRootManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook-root/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook-root/manifests.ts new file mode 100644 index 0000000000..2b6b86549e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook-root/manifests.ts @@ -0,0 +1,13 @@ +import type { ManifestTypes, ManifestWorkspace } from '@umbraco-cms/backoffice/extension-registry'; + +const workspace: ManifestWorkspace = { + type: 'workspace', + alias: 'Umb.Workspace.WebhookRoot', + name: 'Webhook Root Workspace', + element: () => import('./webhook-root-workspace.element.js'), + meta: { + entityType: 'webhook-root', + }, +}; + +export const manifests: Array = [workspace]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/views/overview/webhook-overview-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook-root/webhook-root-workspace.element.ts similarity index 50% rename from src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/views/overview/webhook-overview-view.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook-root/webhook-root-workspace.element.ts index 431370de55..c1b28df40d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/views/overview/webhook-overview-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook-root/webhook-root-workspace.element.ts @@ -1,15 +1,17 @@ -import { UMB_WEBHOOK_COLLECTION_ALIAS } from '../../../collection/index.js'; +import { UMB_WEBHOOK_COLLECTION_ALIAS } from '../../collection/index.js'; import { html, customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -@customElement('umb-webook-root-workspace') +@customElement('umb-webhook-root-workspace') export class UmbWebhookRootWorkspaceElement extends UmbLitElement { render() { - return html` `; + return html` + ; + `; } } -export default UmbWebhookRootWorkspaceElement; +export { UmbWebhookRootWorkspaceElement as element }; declare global { interface HTMLElementTagNameMap { diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/manifests.ts index 6640d60c48..6bdea1ca7b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/manifests.ts @@ -7,9 +7,9 @@ import type { const workspace: ManifestWorkspace = { type: 'workspace', + kind: 'routable', alias: UMB_WEBHOOK_WORKSPACE, name: 'Webhook Root Workspace', - element: () => import('./webhook-workspace.element.js'), api: () => import('./webhook-workspace.context.js'), meta: { entityType: UMB_WEBHOOK_ENTITY_TYPE, @@ -19,9 +19,9 @@ const workspace: ManifestWorkspace = { const workspaceViews: Array = [ { type: 'workspaceView', - alias: 'Umb.WorkspaceView.Webhook.Overview', - name: 'Webhook Root Workspace Overview View', - js: () => import('../views/overview/webhook-overview-view.element.js'), + alias: 'Umb.WorkspaceView.Webhook.Details', + name: 'Webhook Root Workspace Details View', + js: () => import('./views/webhook-details-workspace-view.element.js'), weight: 300, meta: { label: 'Overview', @@ -35,24 +35,6 @@ const workspaceViews: Array = [ }, ], }, - { - type: 'workspaceView', - alias: 'Umb.WorkspaceView.Webhook.Search', - name: 'Webhook Root Workspace Logs View', - js: () => import('../views/overview/webhook-overview-view.element.js'), - weight: 200, - meta: { - label: 'Logs', - pathname: 'logs', - icon: 'icon-box-alt', - }, - conditions: [ - { - alias: 'Umb.Condition.WorkspaceAlias', - match: workspace.alias, - }, - ], - }, ]; export const manifests: Array = [workspace, ...workspaceViews]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/views/webhook-details-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/views/webhook-details-workspace-view.element.ts new file mode 100644 index 0000000000..cf74cea8a6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/views/webhook-details-workspace-view.element.ts @@ -0,0 +1,22 @@ +import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import '@umbraco-cms/backoffice/culture'; + +@customElement('umb-webhook-details-workspace-view') +export class UmbWebhookDetailsWorkspaceViewElement extends UmbLitElement implements UmbWorkspaceViewElement { + render() { + return html`EDIT NPW `; + } + + static styles = [UmbTextStyles, css``]; +} + +export default UmbWebhookDetailsWorkspaceViewElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-webhook-details-workspace-view': UmbWebhookDetailsWorkspaceViewElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace-editor.element.ts new file mode 100644 index 0000000000..08d0ce1f02 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace-editor.element.ts @@ -0,0 +1,89 @@ +import type { UmbWebhookDetailModel } from '../../types.js'; +import { UMB_WEBHOOK_WORKSPACE_CONTEXT } from './webhook-workspace.context-token.js'; +import type { UUIInputElement } from '@umbraco-cms/backoffice/external/uui'; +import { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; +import { css, html, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement, umbFocus } from '@umbraco-cms/backoffice/lit-element'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +@customElement('umb-webhook-workspace-editor') +export class UmbWebhookWorkspaceEditorElement extends UmbLitElement { + #workspaceContext?: typeof UMB_WEBHOOK_WORKSPACE_CONTEXT.TYPE; + + @state() + _webhook?: UmbWebhookDetailModel; + + @state() + _isNew?: boolean; + + constructor() { + super(); + + this.consumeContext(UMB_WEBHOOK_WORKSPACE_CONTEXT, (context) => { + this.#workspaceContext = context; + this.#observeData(); + }); + } + + #observeData() { + if (!this.#workspaceContext) return; + this.observe(this.#workspaceContext.data, (data) => { + this._webhook = data; + }); + this.observe(this.#workspaceContext.isNew, (isNew) => { + this._isNew = isNew; + }); + } + + #handleInput(event: UUIInputEvent) { + if (event instanceof UUIInputEvent) { + const target = event.composedPath()[0] as UUIInputElement; + + if (typeof target?.value === 'string') { + this.#workspaceContext?.setName(target.value); + } + } + } + + render() { + return html``; + } + + static styles = [ + UmbTextStyles, + css` + #header { + display: flex; + padding: 0 var(--uui-size-space-6); + gap: var(--uui-size-space-4); + width: 100%; + } + + uui-input { + width: 100%; + } + + strong { + display: flex; + align-items: center; + } + + #footer-into { + padding: 0 var(--uui-size-layout-1); + } + + uui-input:not(:focus) { + border: 1px solid transparent; + } + `, + ]; +} + +export default UmbWebhookWorkspaceEditorElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-webhook-workspace-editor': UmbWebhookWorkspaceEditorElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace.context-token.ts new file mode 100644 index 0000000000..06f5d38598 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace.context-token.ts @@ -0,0 +1,12 @@ +import type { UmbWebhookWorkspaceContext } from './webhook-workspace.context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import type { UmbSubmittableWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; + +export const UMB_WEBHOOK_WORKSPACE_CONTEXT = new UmbContextToken< + UmbSubmittableWorkspaceContext, + UmbWebhookWorkspaceContext +>( + 'UmbWorkspaceContext', + undefined, + (context): context is UmbWebhookWorkspaceContext => context.getEntityType?.() === 'webhook', +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace.context.ts index 7b1a61f4c9..a3a8b6028a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace.context.ts @@ -1,25 +1,121 @@ -import { UMB_WEBHOOK_ENTITY_TYPE, UMB_WEBHOOK_WORKSPACE } from '../../entity.js'; +import type { UmbWebhookDetailModel } from '../../types.js'; +import { UmbWebhookDetailRepository } from '../../repository/index.js'; +import { UmbWebhookWorkspaceEditorElement } from './webhook-workspace-editor.element.js'; +import { + type UmbSubmittableWorkspaceContext, + UmbSubmittableWorkspaceContextBase, + UmbWorkspaceRouteManager, + UmbWorkspaceIsNewRedirectController, + type UmbRoutableWorkspaceContext, +} from '@umbraco-cms/backoffice/workspace'; +import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; -import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; -import type { UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; -import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; -export class UmbWebhookWorkspaceContext extends UmbControllerBase implements UmbWorkspaceContext { - public readonly workspaceAlias = UMB_WEBHOOK_WORKSPACE; +export class UmbWebhookWorkspaceContext + extends UmbSubmittableWorkspaceContextBase + implements UmbSubmittableWorkspaceContext, UmbRoutableWorkspaceContext +{ + public readonly repository: UmbWebhookDetailRepository = new UmbWebhookDetailRepository(this); - getEntityType() { - return UMB_WEBHOOK_ENTITY_TYPE; - } + #data = new UmbObjectState(undefined); + readonly data = this.#data.asObservable(); + + readonly unique = this.#data.asObservablePart((data) => data?.unique); + readonly name = this.#data.asObservablePart((data) => data?.name); + + readonly routes = new UmbWorkspaceRouteManager(this); constructor(host: UmbControllerHost) { - super(host); - this.provideContext(UMB_WORKSPACE_CONTEXT, this); - // TODO: Revisit usage of workspace for this case... currently no other workspace context provides them self with their own token. - this.provideContext(UMB_APP_WEBHOOK_CONTEXT, this); + super(host, 'Umb.Workspace.Webhook'); + + this.routes.setRoutes([ + { + path: 'create', + component: UmbWebhookWorkspaceEditorElement, + setup: async () => { + this.create(); + + new UmbWorkspaceIsNewRedirectController( + this, + this, + this.getHostElement().shadowRoot!.querySelector('umb-router-slot')!, + ); + }, + }, + { + path: 'edit/:unique', + component: UmbWebhookWorkspaceEditorElement, + setup: (_component, info) => { + this.removeUmbControllerByAlias('isNewRedirectController'); + this.load(info.match.params.unique); + }, + }, + ]); + } + + protected resetState(): void { + super.resetState(); + this.#data.setValue(undefined); + } + + async load(unique: string) { + this.resetState(); + const { data } = await this.repository.requestByUnique(unique); + if (data) { + this.setIsNew(false); + this.#data.update(data); + } + } + + async create() { + this.resetState(); + const { data } = await this.repository.createScaffold(); + if (!data) return; + this.setIsNew(true); + this.#data.update(data); + return { data }; + } + + getData() { + return this.#data.getValue(); + } + + getEntityType() { + return 'webhook'; + } + + getUnique() { + return this.#data.getValue()?.unique; + } + + setName(name: string) { + this.#data.update({ name }); + } + + async submit() { + const newData = this.getData(); + if (!newData) { + throw new Error('No data to submit'); + } + + if (this.getIsNew()) { + const { error } = await this.repository.create(newData); + if (error) { + throw new Error(error.message); + } + this.setIsNew(false); + } else { + const { error } = await this.repository.save(newData); + if (error) { + throw new Error(error.message); + } + } + } + + destroy(): void { + this.#data.destroy(); + super.destroy(); } } export { UmbWebhookWorkspaceContext as api }; - -export const UMB_APP_WEBHOOK_CONTEXT = new UmbContextToken(UmbWebhookWorkspaceContext.name); diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace.element.ts deleted file mode 100644 index 4818a56269..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace.element.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { html, customElement } from '@umbraco-cms/backoffice/external/lit'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; - -@customElement('umb-webhook-workspace') -export class UmbWebhookWorkspaceElement extends UmbLitElement { - render() { - return html` - `; - } -} - -export { UmbWebhookWorkspaceElement as element }; - -declare global { - interface HTMLElementTagNameMap { - 'umb-webhook-workspace': UmbWebhookWorkspaceElement; - } -} From 6aefedfdf74fbfce31ea809975fc2503fb569e8b Mon Sep 17 00:00:00 2001 From: JesmoDev <26099018+JesmoDev@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:38:30 +0200 Subject: [PATCH 002/120] cleanup --- .../webhook/webhook-workspace-editor.element.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace-editor.element.ts index 08d0ce1f02..997b147c70 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace-editor.element.ts @@ -34,16 +34,6 @@ export class UmbWebhookWorkspaceEditorElement extends UmbLitElement { }); } - #handleInput(event: UUIInputEvent) { - if (event instanceof UUIInputEvent) { - const target = event.composedPath()[0] as UUIInputElement; - - if (typeof target?.value === 'string') { - this.#workspaceContext?.setName(target.value); - } - } - } - render() { return html` Date: Tue, 30 Apr 2024 11:40:18 +0200 Subject: [PATCH 003/120] cleanup --- .../webhook-workspace-editor.element.ts | 56 +------------------ 1 file changed, 1 insertion(+), 55 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace-editor.element.ts index 997b147c70..80ab0bc02e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/webhook-workspace-editor.element.ts @@ -7,67 +7,13 @@ import { UmbLitElement, umbFocus } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; @customElement('umb-webhook-workspace-editor') export class UmbWebhookWorkspaceEditorElement extends UmbLitElement { - #workspaceContext?: typeof UMB_WEBHOOK_WORKSPACE_CONTEXT.TYPE; - - @state() - _webhook?: UmbWebhookDetailModel; - - @state() - _isNew?: boolean; - - constructor() { - super(); - - this.consumeContext(UMB_WEBHOOK_WORKSPACE_CONTEXT, (context) => { - this.#workspaceContext = context; - this.#observeData(); - }); - } - - #observeData() { - if (!this.#workspaceContext) return; - this.observe(this.#workspaceContext.data, (data) => { - this._webhook = data; - }); - this.observe(this.#workspaceContext.isNew, (isNew) => { - this._isNew = isNew; - }); - } - render() { return html``; } - static styles = [ - UmbTextStyles, - css` - #header { - display: flex; - padding: 0 var(--uui-size-space-6); - gap: var(--uui-size-space-4); - width: 100%; - } - - uui-input { - width: 100%; - } - - strong { - display: flex; - align-items: center; - } - - #footer-into { - padding: 0 var(--uui-size-layout-1); - } - - uui-input:not(:focus) { - border: 1px solid transparent; - } - `, - ]; + static styles = [UmbTextStyles, css``]; } export default UmbWebhookWorkspaceEditorElement; From 9605cb6545fbffedff7d0e598f6d6d01a15dca1b Mon Sep 17 00:00:00 2001 From: JesmoDev <26099018+JesmoDev@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:49:36 +0200 Subject: [PATCH 004/120] workspace edit view --- .../webhook-details-workspace-view.element.ts | 61 +++++++++++++++++-- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/views/webhook-details-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/views/webhook-details-workspace-view.element.ts index cf74cea8a6..c5cedd9c05 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/views/webhook-details-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/views/webhook-details-workspace-view.element.ts @@ -1,16 +1,69 @@ -import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UMB_WEBHOOK_WORKSPACE_CONTEXT } from '../webhook-workspace.context-token.js'; +import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import '@umbraco-cms/backoffice/culture'; +import type { UmbWebhookDetailModel } from '@umbraco-cms/backoffice/webhook'; @customElement('umb-webhook-details-workspace-view') export class UmbWebhookDetailsWorkspaceViewElement extends UmbLitElement implements UmbWorkspaceViewElement { - render() { - return html`EDIT NPW `; + @state() + _webhook?: UmbWebhookDetailModel; + + @state() + _isNew?: boolean; + + #webhookWorkspaceContext?: typeof UMB_WEBHOOK_WORKSPACE_CONTEXT.TYPE; + + constructor() { + super(); + + this.consumeContext(UMB_WEBHOOK_WORKSPACE_CONTEXT, (instance) => { + this.#webhookWorkspaceContext = instance; + this.observe(this.#webhookWorkspaceContext.data, (webhook) => { + this._webhook = webhook; + }); + this.observe(this.#webhookWorkspaceContext.isNew, (isNew) => { + this._isNew = isNew; + }); + }); } - static styles = [UmbTextStyles, css``]; + render() { + return html` + + + + + +
IMPLEMENT
+
+ + + + + + + +
+ `; + } + + static styles = [ + UmbTextStyles, + css` + :host { + display: block; + padding: var(--uui-size-space-6); + } + `, + ]; } export default UmbWebhookDetailsWorkspaceViewElement; From fc525995b1e532f24198242aa559ad2cb47acc05 Mon Sep 17 00:00:00 2001 From: JesmoDev <26099018+JesmoDev@users.noreply.github.com> Date: Tue, 30 Apr 2024 13:57:16 +0200 Subject: [PATCH 005/120] use picker --- .../views/webhook-details-workspace-view.element.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/views/webhook-details-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/views/webhook-details-workspace-view.element.ts index c5cedd9c05..4da5c0685d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/views/webhook-details-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/workspace/webhook/views/webhook-details-workspace-view.element.ts @@ -39,12 +39,9 @@ export class UmbWebhookDetailsWorkspaceViewElement extends UmbLitElement impleme
IMPLEMENT
- + + + From 892fa770669fc4de6e23c59bf750a4bb22eb6d0c Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 30 Apr 2024 17:52:27 +0100 Subject: [PATCH 006/120] Media Collection View fixes Aligns with Document Collection View fixes, PR #1737 - Adds loading state - Fixes open media editor modal --- .../media-collection.server.data-source.ts | 3 + .../packages/media/media/collection/types.ts | 9 ++ .../media-grid-collection-view.element.ts | 79 +++++++++++----- .../media-table-column-name.element.ts | 48 ++++------ .../media-table-collection-view.element.ts | 89 +++++++++++++------ 5 files changed, 146 insertions(+), 82 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/repository/media-collection.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/repository/media-collection.server.data-source.ts index b75fadfff8..19c75cb63a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/repository/media-collection.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/repository/media-collection.server.data-source.ts @@ -32,12 +32,15 @@ export class UmbMediaCollectionServerDataSource implements UmbCollectionDataSour const model: UmbMediaCollectionItemModel = { unique: item.id, + entityType: 'media', + contentTypeAlias: item.mediaType.alias, createDate: new Date(variant.createDate), creator: item.creator, icon: item.mediaType.icon, name: variant.name, sortOrder: item.sortOrder, updateDate: new Date(variant.updateDate), + updater: item.creator, // TODO: Check if the `updater` is available for media items. [LK] values: item.values.map((item) => { return { alias: item.alias, value: item.value as string }; }), diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts index 1ae06b64f7..57d968794a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts @@ -10,11 +10,20 @@ export interface UmbMediaCollectionFilterModel extends UmbCollectionFilterModel export interface UmbMediaCollectionItemModel { unique: string; + entityType: string; + contentTypeAlias: string; createDate: Date; creator?: string | null; icon: string; name: string; sortOrder: number; updateDate: Date; + updater?: string | null; values: Array<{ alias: string; value: string }>; } + +export interface UmbEditableMediaCollectionItemModel { + item: UmbMediaCollectionItemModel; + editPath: string; +} + diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts index 6a77f11469..da754c7398 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts @@ -1,12 +1,16 @@ -import type { UmbMediaCollectionFilterModel, UmbMediaCollectionItemModel } from '../../types.js'; -import { css, html, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit'; +import type { UmbMediaCollectionItemModel } from '../../types.js'; +import type { UmbMediaCollectionContext } from '../../media-collection.context.js'; +import { css, customElement, html, nothing, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection'; -import type { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection'; +import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal'; @customElement('umb-media-grid-collection-view') export class UmbMediaGridCollectionViewElement extends UmbLitElement { + @state() + private _editMediaPath = ''; + @state() private _items: Array = []; @@ -16,7 +20,7 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { @state() private _selection: Array = []; - #collectionContext?: UmbDefaultCollectionContext; + #collectionContext?: UmbMediaCollectionContext; constructor() { super(); @@ -24,23 +28,41 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { this.#collectionContext = collectionContext; this.#observeCollectionContext(); }); + + new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) + .addAdditionalPath('media') + .onSetup(() => { + return { data: { entityType: 'media', preset: {} } }; + }) + .onReject(() => { + this.#collectionContext?.requestCollection(); + }) + .onSubmit(() => { + this.#collectionContext?.requestCollection(); + }) + .observeRouteBuilder((routeBuilder) => { + this._editMediaPath = routeBuilder({}); + }); } #observeCollectionContext() { if (!this.#collectionContext) return; - this.observe(this.#collectionContext.items, (items) => (this._items = items), 'umbCollectionItemsObserver'); + this.observe(this.#collectionContext.loading, (loading) => (this._loading = loading), '_observeLoading'); + + this.observe(this.#collectionContext.items, (items) => (this._items = items), '_observeItems'); this.observe( this.#collectionContext.selection.selection, (selection) => (this._selection = selection), - 'umbCollectionSelectionObserver', + '_observeSelection', ); } - #onOpen(item: UmbMediaCollectionItemModel) { - //TODO: Fix when we have dynamic routing - history.pushState(null, '', 'section/media/workspace/media/edit/' + item.unique); + #onOpen(event: Event, id: string) { + event.preventDefault(); + event.stopPropagation(); + window.history.pushState(null, '', this._editMediaPath + 'edit/' + id); } #onSelect(item: UmbMediaCollectionItemModel) { @@ -60,26 +82,37 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { } render() { - if (this._loading) { - return html`
`; - } - - if (this._items.length === 0) { - return html`

${this.localize.term('content_listViewNoItems')}

`; - } + return this._items.length === 0 ? this.#renderEmpty() : this.#renderItems(); + } + #renderEmpty() { + if (this._items.length > 0) return nothing; return html` -
- ${repeat( - this._items, - (item) => item.unique, - (item) => this.#renderCard(item), +
+ ${when( + this._loading, + () => html``, + () => html`

${this.localize.term('content_listViewNoItems')}

`, )}
`; } - #renderCard(item: UmbMediaCollectionItemModel) { + #renderItems() { + if (this._items.length === 0) return nothing; + return html` +
+ ${repeat( + this._items, + (item) => item.unique, + (item) => this.#renderItem(item), + )} +
+ ${when(this._loading, () => html``)} + `; + } + + #renderItem(item: UmbMediaCollectionItemModel) { // TODO: Fix the file extension when media items have a file extension. [?] return html` 0} ?selected=${this.#isSelected(item)} - @open=${() => this.#onOpen(item)} + @open=${(event: Event) => this.#onOpen(event, item.unique)} @selected=${() => this.#onSelect(item)} @deselected=${() => this.#onDeselect(item)} class="media-item" diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/column-layouts/media-table-column-name.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/column-layouts/media-table-column-name.element.ts index b1a10fb899..688b2a90c3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/column-layouts/media-table-column-name.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/column-layouts/media-table-column-name.element.ts @@ -1,50 +1,32 @@ -import type { UmbMediaCollectionItemModel } from '../../../types.js'; -import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; +import type { UmbEditableMediaCollectionItemModel } from '../../../types.js'; +import { css, customElement, html, nothing, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal'; import type { UmbTableColumn, UmbTableColumnLayoutElement, UmbTableItem } from '@umbraco-cms/backoffice/components'; +import type { UUIButtonElement } from '@umbraco-cms/backoffice/external/uui'; @customElement('umb-media-table-column-name') export class UmbMediaTableColumnNameElement extends UmbLitElement implements UmbTableColumnLayoutElement { - @state() - private _editMediaPath = ''; - - @property({ type: Object, attribute: false }) column!: UmbTableColumn; - - @property({ type: Object, attribute: false }) item!: UmbTableItem; @property({ attribute: false }) - value!: UmbMediaCollectionItemModel; + value!: UmbEditableMediaCollectionItemModel; - constructor() { - super(); - - new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) - .addAdditionalPath('media') - .onSetup(() => { - return { data: { entityType: 'media', preset: {} } }; - }) - .observeRouteBuilder((routeBuilder) => { - this._editMediaPath = routeBuilder({}); - }); - } - - #onClick(event: Event) { - // TODO: [LK] Review the `stopPropagation` usage, as it causes a page reload. - // But we still need a say to prevent the `umb-table` from triggering a selection event. + #onClick(event: Event & { target: UUIButtonElement }) { + event.preventDefault(); event.stopPropagation(); + window.history.pushState(null, '', event.target.href); } render() { - return html``; + if (!this.value) return nothing; + return html` + + `; } static styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts index a3a4cef9a6..9e42f79991 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts @@ -1,6 +1,6 @@ import type { UmbCollectionColumnConfiguration } from '../../../../../core/collection/types.js'; import type { UmbMediaCollectionFilterModel, UmbMediaCollectionItemModel } from '../../types.js'; -import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { css, customElement, html, nothing, state, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection'; @@ -14,6 +14,8 @@ import type { UmbTableOrderedEvent, UmbTableSelectedEvent, } from '@umbraco-cms/backoffice/components'; +import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal'; +import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/modal'; import './column-layouts/media-table-column-name.element.js'; @@ -51,29 +53,52 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { @state() private _selection: Array = []; - @state() - private _skip: number = 0; - #collectionContext?: UmbDefaultCollectionContext; + #routeBuilder?: UmbModalRouteBuilder; + constructor() { super(); this.consumeContext(UMB_COLLECTION_CONTEXT, (collectionContext) => { this.#collectionContext = collectionContext; - this.#observeCollectionContext(); }); + + this.#registerModalRoute(); + } + + #registerModalRoute() { + new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) + .addAdditionalPath(':entityType') + .onSetup((params) => { + return { data: { entityType: params.entityType, preset: {} } }; + }) + .onReject(() => { + this.#collectionContext?.requestCollection(); + }) + .onSubmit(() => { + this.#collectionContext?.requestCollection(); + }) + .observeRouteBuilder((routeBuilder) => { + this.#routeBuilder = routeBuilder; + + // NOTE: Configuring the observations AFTER the route builder is ready, + // otherwise there is a race condition and `#collectionContext.items` tends to win. [LK] + this.#observeCollectionContext(); + }); } #observeCollectionContext() { if (!this.#collectionContext) return; + this.observe(this.#collectionContext.loading, (loading) => (this._loading = loading), '_observeLoading'); + this.observe( this.#collectionContext.userDefinedProperties, (userDefinedProperties) => { this._userDefinedProperties = userDefinedProperties; this.#createTableHeadings(); }, - 'umbCollectionUserDefinedPropertiesObserver', + '_observeUserDefinedProperties', ); this.observe( @@ -82,7 +107,7 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { this._items = items; this.#createTableItems(this._items); }, - 'umbCollectionItemsObserver', + '_observeItems', ); this.observe( @@ -90,15 +115,7 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { (selection) => { this._selection = selection as string[]; }, - 'umbCollectionSelectionObserver', - ); - - this.observe( - this.#collectionContext.pagination.skip, - (skip) => { - this._skip = skip; - }, - 'umbCollectionSkipObserver', + '_observeSelection', ); } @@ -129,15 +146,20 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { const data = this._tableColumns?.map((column) => { + const editPath = this.#routeBuilder + ? this.#routeBuilder({ entityType: item.entityType }) + `edit/${item.unique}` + : ''; + return { columnAlias: column.alias, - value: column.elementName ? item : this.#getPropertyValueByAlias(item, column.alias), + value: column.elementName ? { item, editPath } : this.#getPropertyValueByAlias(item, column.alias), }; }) ?? []; return { id: item.unique, icon: item.icon, + entityType: 'media', data: data, }; }); @@ -145,6 +167,8 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { #getPropertyValueByAlias(item: UmbMediaCollectionItemModel, alias: string) { switch (alias) { + case 'contentTypeAlias': + return item.contentTypeAlias; case 'createDate': return item.createDate.toLocaleString(); case 'name': @@ -156,6 +180,8 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { return item.sortOrder; case 'updateDate': return item.updateDate.toLocaleString(); + case 'updater': + return item.updater; default: return item.values.find((value) => value.alias === alias)?.value ?? ''; } @@ -186,23 +212,34 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { } render() { - if (this._loading) { - return html`
`; - } + return this._tableItems.length === 0 ? this.#renderEmpty() : this.#renderItems(); + } - if (this._tableItems.length === 0) { - return html`

${this.localize.term('content_listViewNoItems')}

`; - } + #renderEmpty() { + if (this._tableItems.length > 0) return nothing; + return html` +
+ ${when( + this._loading, + () => html``, + () => html`

${this.localize.term('content_listViewNoItems')}

`, + )} +
+ `; + } + #renderItems() { + if (this._tableItems.length === 0) return nothing; return html` + @selected=${this.#handleSelect} + @deselected=${this.#handleDeselect} + @ordered=${this.#handleOrdering}> + ${when(this._loading, () => html``)} `; } From f4ae2637369ebce9f9a4921037806b13ec184a0b Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 1 May 2024 09:44:32 +0100 Subject: [PATCH 007/120] Replaced hardcoded edit path with constant helper Added stub file for media path constants helper. --- .../views/grid/media-grid-collection-view.element.ts | 7 +++++-- .../views/table/media-table-collection-view.element.ts | 4 +++- .../src/packages/media/media/index.ts | 1 + .../src/packages/media/media/paths.ts | 3 +++ 4 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/paths.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts index da754c7398..43201b8839 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts @@ -1,3 +1,4 @@ +import { UMB_EDIT_MEDIA_WORKSPACE_PATH_PATTERN } from '../../../paths.js'; import type { UmbMediaCollectionItemModel } from '../../types.js'; import type { UmbMediaCollectionContext } from '../../media-collection.context.js'; import { css, customElement, html, nothing, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; @@ -59,10 +60,12 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { ); } - #onOpen(event: Event, id: string) { + #onOpen(event: Event, unique: string) { event.preventDefault(); event.stopPropagation(); - window.history.pushState(null, '', this._editMediaPath + 'edit/' + id); + + const url = this._editMediaPath + UMB_EDIT_MEDIA_WORKSPACE_PATH_PATTERN.generateLocal({ unique }); + window.history.pushState(null, '', url); } #onSelect(item: UmbMediaCollectionItemModel) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts index 9e42f79991..86d2451247 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts @@ -1,3 +1,4 @@ +import { UMB_EDIT_MEDIA_WORKSPACE_PATH_PATTERN } from '../../../paths.js'; import type { UmbCollectionColumnConfiguration } from '../../../../../core/collection/types.js'; import type { UmbMediaCollectionFilterModel, UmbMediaCollectionItemModel } from '../../types.js'; import { css, customElement, html, nothing, state, when } from '@umbraco-cms/backoffice/external/lit'; @@ -147,7 +148,8 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { const data = this._tableColumns?.map((column) => { const editPath = this.#routeBuilder - ? this.#routeBuilder({ entityType: item.entityType }) + `edit/${item.unique}` + ? this.#routeBuilder({ entityType: item.entityType }) + + UMB_EDIT_MEDIA_WORKSPACE_PATH_PATTERN.generateLocal({ unique: item.unique }) : ''; return { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts index 774113923c..dd00d3aa7a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts @@ -5,6 +5,7 @@ export * from './workspace/index.js'; export * from './reference/index.js'; export * from './components/index.js'; export * from './entity.js'; +export * from './paths.js'; export * from './utils/index.js'; export { UMB_MEDIA_TREE_ALIAS, UMB_MEDIA_TREE_PICKER_MODAL } from './tree/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/paths.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/paths.ts new file mode 100644 index 0000000000..d5eb6fdfd5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/paths.ts @@ -0,0 +1,3 @@ +import { UmbPathPattern } from '@umbraco-cms/backoffice/router'; + +export const UMB_EDIT_MEDIA_WORKSPACE_PATH_PATTERN = new UmbPathPattern<{ unique: string }>('edit/:unique'); From c7d71c48dfebb38a1ce216f45091aae5383eaa86 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 1 May 2024 13:49:33 +0100 Subject: [PATCH 008/120] Collection: Syncs the Order By with Columns Displayed field --- .../config/order-by/order-by.element.ts | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/order-by/order-by.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/order-by/order-by.element.ts index 39ccfe8ba2..977830cf4c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/order-by/order-by.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/order-by/order-by.element.ts @@ -1,34 +1,44 @@ -import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; -import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; -import type { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; -import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; -import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; +import type { UmbCollectionColumnConfiguration } from '../../../../core/collection/types.js'; +import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; +import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; +import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; +import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; +import type { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; /** * @element umb-property-editor-ui-collection-order-by */ @customElement('umb-property-editor-ui-collection-order-by') export class UmbPropertyEditorUICollectionOrderByElement extends UmbLitElement implements UmbPropertyEditorUiElement { - private _value = ''; @property() - public set value(v: string) { - this._value = v; - this._options = this._options.map((option) => (option.value === v ? { ...option, selected: true } : option)); - } - public get value() { - return this._value; - } + public value: string = ''; + + public config?: UmbPropertyEditorConfigCollection; @state() - _options: Array