diff --git a/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts b/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts index 10eb00ca0a..315fd1ff4d 100644 --- a/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts +++ b/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts @@ -1,7 +1,6 @@ import { manifests as workspaceViewManifests } from './views/manifests.js'; import { UMB_BLOCK_GRID_AREA_TYPE_WORKSPACE_ALIAS } from './index.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; +import { UmbSubmitWorkspaceAction, UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ ...workspaceViewManifests, diff --git a/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts b/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts index ba7238c442..3bdf1271b9 100644 --- a/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts +++ b/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_BLOCK_GRID_AREA_TYPE_WORKSPACE_ALIAS } from '../index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import type { ManifestWorkspaceView } from '@umbraco-cms/backoffice/workspace'; export const workspaceViews: Array = [ diff --git a/src/packages/block/block-grid/workspace/views/manifests.ts b/src/packages/block/block-grid/workspace/views/manifests.ts index 8722b9f764..e14984f6c2 100644 --- a/src/packages/block/block-grid/workspace/views/manifests.ts +++ b/src/packages/block/block-grid/workspace/views/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_BLOCK_GRID_TYPE_WORKSPACE_ALIAS } from '../index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/block/block-list/workspace/views/manifests.ts b/src/packages/block/block-list/workspace/views/manifests.ts index 04d75d39ab..a019927e9c 100644 --- a/src/packages/block/block-list/workspace/views/manifests.ts +++ b/src/packages/block/block-list/workspace/views/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_BLOCK_LIST_TYPE_WORKSPACE_ALIAS } from '../index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/block/block-rte/workspace/views/manifests.ts b/src/packages/block/block-rte/workspace/views/manifests.ts index 5b8abf2041..f075a1e992 100644 --- a/src/packages/block/block-rte/workspace/views/manifests.ts +++ b/src/packages/block/block-rte/workspace/views/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_BLOCK_RTE_TYPE_WORKSPACE_ALIAS } from '../index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/block/block-type/workspace/manifests.ts b/src/packages/block/block-type/workspace/manifests.ts index 3fcdb6fb10..423d1a2a51 100644 --- a/src/packages/block/block-type/workspace/manifests.ts +++ b/src/packages/block/block-type/workspace/manifests.ts @@ -1,8 +1,7 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_BLOCK_GRID_TYPE_WORKSPACE_ALIAS } from '../../block-grid/workspace/index.js'; import { UMB_BLOCK_LIST_TYPE_WORKSPACE_ALIAS } from '../../block-list/workspace/index.js'; import { UMB_BLOCK_RTE_TYPE_WORKSPACE_ALIAS } from '../../block-rte/workspace/index.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/block/block/workspace/manifests.ts b/src/packages/block/block/workspace/manifests.ts index 1b67a6e46e..89c0388d5a 100644 --- a/src/packages/block/block/workspace/manifests.ts +++ b/src/packages/block/block/workspace/manifests.ts @@ -1,6 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_BLOCK_WORKSPACE_ALIAS } from './index.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/core/content-type/index.ts b/src/packages/core/content-type/index.ts index 79f883296b..2a3cd65724 100644 --- a/src/packages/core/content-type/index.ts +++ b/src/packages/core/content-type/index.ts @@ -1,7 +1,12 @@ -export * from './components/index.js'; export * from './composition/index.js'; export * from './modals/index.js'; export * from './repository/index.js'; export * from './structure/index.js'; export * from './workspace/index.js'; export type * from './types.js'; + +/** + * @deprecated Use `UmbPropertyTypeBasedPropertyElement` from `@umbraco-cms/backoffice/content` instead. + * Export will be removed in version 17. + */ +export { UmbPropertyTypeBasedPropertyElement } from '../content/components/property-type-based-property/property-type-based-property.element.js'; diff --git a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts index ead29d145b..160866835c 100644 --- a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts +++ b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts @@ -1,12 +1,9 @@ +import type { UmbContentTypeModel, UmbPropertyTypeContainerModel } from '../../../types.js'; +import type { UmbContentTypeContainerStructureHelper } from '../../../structure/index.js'; import { css, customElement, html, nothing, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; import { UmbLitElement, umbFocus } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import type { - UmbContentTypeContainerStructureHelper, - UmbContentTypeModel, - UmbPropertyTypeContainerModel, -} from '@umbraco-cms/backoffice/content-type'; import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; import './content-type-design-editor-properties.element.js'; diff --git a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-properties.element.ts b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-properties.element.ts index 65449ac606..e85a4f7d5b 100644 --- a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-properties.element.ts +++ b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-properties.element.ts @@ -1,4 +1,6 @@ import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT } from '../../content-type-workspace.context-token.js'; +import type { UmbContentTypeModel, UmbPropertyTypeModel } from '../../../types.js'; +import { UmbContentTypePropertyStructureHelper } from '../../../structure/index.js'; import { UMB_CONTENT_TYPE_DESIGN_EDITOR_CONTEXT } from './content-type-design-editor.context.js'; import type { UmbContentTypeDesignEditorPropertyElement } from './content-type-design-editor-property.element.js'; import { @@ -13,17 +15,15 @@ import { } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { UmbContentTypePropertyStructureHelper } from '@umbraco-cms/backoffice/content-type'; -import type { UmbContentTypeModel, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type'; import { type UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; - -import './content-type-design-editor-property.element.js'; import { UMB_CREATE_PROPERTY_TYPE_WORKSPACE_PATH_PATTERN, UMB_PROPERTY_TYPE_WORKSPACE_MODAL, } from '@umbraco-cms/backoffice/property-type'; +import './content-type-design-editor-property.element.js'; + const SORTER_CONFIG: UmbSorterConfig = { getUniqueOfElement: (element) => { return element.getAttribute('data-umb-property-id'); diff --git a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts index 02374f4f8a..571b9a3c69 100644 --- a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts +++ b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts @@ -1,3 +1,5 @@ +import type { UmbContentTypePropertyStructureHelper } from '../../../structure/index.js'; +import type { UmbContentTypeModel, UmbPropertyTypeModel, UmbPropertyTypeScaffoldModel } from '../../../types.js'; import { UmbPropertyTypeContext } from './content-type-design-editor-property.context.js'; import { css, html, customElement, property, state, nothing } from '@umbraco-cms/backoffice/external/lit'; import { generateAlias } from '@umbraco-cms/backoffice/utils'; @@ -6,12 +8,6 @@ import { UmbDataTypeDetailRepository } from '@umbraco-cms/backoffice/data-type'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UMB_EDIT_PROPERTY_TYPE_WORKSPACE_PATH_PATTERN } from '@umbraco-cms/backoffice/property-type'; -import type { - UmbContentTypeModel, - UmbContentTypePropertyStructureHelper, - UmbPropertyTypeModel, - UmbPropertyTypeScaffoldModel, -} from '@umbraco-cms/backoffice/content-type'; import type { UUIInputElement, UUIInputLockElement, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; /** diff --git a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-tab.element.ts b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-tab.element.ts index 3b18576327..363f959008 100644 --- a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-tab.element.ts +++ b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-tab.element.ts @@ -1,13 +1,13 @@ import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT } from '../../content-type-workspace.context-token.js'; +import type { UmbContentTypeModel, UmbPropertyTypeContainerModel } from '../../../types.js'; +import { UmbContentTypeContainerStructureHelper } from '../../../structure/index.js'; import { UMB_CONTENT_TYPE_DESIGN_EDITOR_CONTEXT } from './content-type-design-editor.context.js'; import type { UmbContentTypeWorkspaceViewEditGroupElement } from './content-type-design-editor-group.element.js'; import { css, customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit'; -import { UmbContentTypeContainerStructureHelper } from '@umbraco-cms/backoffice/content-type'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; -import type { UmbContentTypeModel, UmbPropertyTypeContainerModel } from '@umbraco-cms/backoffice/content-type'; import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter'; import './content-type-design-editor-properties.element.js'; diff --git a/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts b/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts index 9105d91fd0..50d10eef50 100644 --- a/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts +++ b/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts @@ -1,15 +1,14 @@ import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT } from '../../content-type-workspace.context-token.js'; +import type { UmbContentTypeModel, UmbPropertyTypeContainerModel } from '../../../types.js'; +import { + UmbContentTypeContainerStructureHelper, + UmbContentTypeMoveRootGroupsIntoFirstTabHelper, +} from '../../../structure/index.js'; +import { UMB_COMPOSITION_PICKER_MODAL } from '../../../modals/index.js'; import type { UmbContentTypeDesignEditorTabElement } from './content-type-design-editor-tab.element.js'; import { UmbContentTypeDesignEditorContext } from './content-type-design-editor.context.js'; import { css, html, customElement, state, repeat, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit'; import type { UUIInputElement, UUIInputEvent, UUITabElement } from '@umbraco-cms/backoffice/external/uui'; -import { - UMB_COMPOSITION_PICKER_MODAL, - UmbContentTypeContainerStructureHelper, - UmbContentTypeMoveRootGroupsIntoFirstTabHelper, - type UmbContentTypeModel, - type UmbPropertyTypeContainerModel, -} from '@umbraco-cms/backoffice/content-type'; import { encodeFolderName } from '@umbraco-cms/backoffice/router'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { CompositionTypeModel } from '@umbraco-cms/backoffice/external/backend-api'; diff --git a/src/packages/core/content-type/components/index.ts b/src/packages/core/content/components/index.ts similarity index 100% rename from src/packages/core/content-type/components/index.ts rename to src/packages/core/content/components/index.ts diff --git a/src/packages/core/content-type/components/property-type-based-property/index.ts b/src/packages/core/content/components/property-type-based-property/index.ts similarity index 100% rename from src/packages/core/content-type/components/property-type-based-property/index.ts rename to src/packages/core/content/components/property-type-based-property/index.ts diff --git a/src/packages/core/content-type/components/property-type-based-property/property-type-based-property.element.ts b/src/packages/core/content/components/property-type-based-property/property-type-based-property.element.ts similarity index 95% rename from src/packages/core/content-type/components/property-type-based-property/property-type-based-property.element.ts rename to src/packages/core/content/components/property-type-based-property/property-type-based-property.element.ts index 475188cd24..745b97c7c0 100644 --- a/src/packages/core/content-type/components/property-type-based-property/property-type-based-property.element.ts +++ b/src/packages/core/content/components/property-type-based-property/property-type-based-property.element.ts @@ -1,7 +1,6 @@ -import type { UmbPropertyEditorConfig } from '../../../property-editor/index.js'; -import type { UmbPropertyTypeModel } from '../../types.js'; +import { UmbContentPropertyContext } from '../../content-property.context.js'; +import type { UmbPropertyEditorConfig } from '@umbraco-cms/backoffice/property-editor'; import { css, customElement, html, ifDefined, property, state } from '@umbraco-cms/backoffice/external/lit'; -import { UmbContentPropertyContext } from '@umbraco-cms/backoffice/content'; import { UmbDataTypeDetailRepository } from '@umbraco-cms/backoffice/data-type'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -9,6 +8,7 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UmbDataTypeDetailModel } from '@umbraco-cms/backoffice/data-type'; import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; import { UMB_UNSUPPORTED_EDITOR_SCHEMA_ALIASES } from '@umbraco-cms/backoffice/property'; +import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type'; @customElement('umb-property-type-based-property') export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement { diff --git a/src/packages/core/content/constants.ts b/src/packages/core/content/constants.ts index cecd2baf55..05a27ced5d 100644 --- a/src/packages/core/content/constants.ts +++ b/src/packages/core/content/constants.ts @@ -1 +1 @@ -export const UMB_CONTENT_SECTION_ALIAS = 'Umb.Section.Content'; \ No newline at end of file +export const UMB_CONTENT_SECTION_ALIAS = 'Umb.Section.Content'; diff --git a/src/packages/core/content/controller/merge-content-variant-data.controller.ts b/src/packages/core/content/controller/merge-content-variant-data.controller.ts index aa50029af8..ae4e35af83 100644 --- a/src/packages/core/content/controller/merge-content-variant-data.controller.ts +++ b/src/packages/core/content/controller/merge-content-variant-data.controller.ts @@ -1,5 +1,5 @@ +import type { UmbContentLikeDetailModel, UmbPotentialContentValueModel } from '../types.js'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; -import type { UmbContentLikeDetailModel, UmbPotentialContentValueModel } from '@umbraco-cms/backoffice/content'; import { createExtensionApi } from '@umbraco-cms/backoffice/extension-api'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbVariantId, type UmbVariantDataModel } from '@umbraco-cms/backoffice/variant'; diff --git a/src/packages/core/content/index.ts b/src/packages/core/content/index.ts index 1fe642380a..338c9631c1 100644 --- a/src/packages/core/content/index.ts +++ b/src/packages/core/content/index.ts @@ -1,9 +1,12 @@ export { UMB_CONTENT_PROPERTY_CONTEXT } from './content-property.context-token.js'; export { UmbContentPropertyContext } from './content-property.context.js'; + export * from './collection/index.js'; +export * from './components/index.js'; export * from './constants.js'; export * from './controller/merge-content-variant-data.controller.js'; export * from './manager/index.js'; export * from './property-dataset-context/index.js'; +export * from './variant-picker/index.js'; export * from './workspace/index.js'; export type * from './types.js'; diff --git a/src/packages/core/content/manager/content-data-manager.ts b/src/packages/core/content/manager/content-data-manager.ts index 3c6601037f..d8d375eb4c 100644 --- a/src/packages/core/content/manager/content-data-manager.ts +++ b/src/packages/core/content/manager/content-data-manager.ts @@ -1,5 +1,5 @@ +import type { UmbContentDetailModel } from '../types.js'; import { UmbElementWorkspaceDataManager } from './element-data-manager.js'; -import type { UmbContentDetailModel } from '@umbraco-cms/backoffice/content'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { appendToFrozenArray, jsonStringComparison } from '@umbraco-cms/backoffice/observable-api'; import { UmbVariantId, type UmbEntityVariantModel } from '@umbraco-cms/backoffice/variant'; @@ -14,11 +14,20 @@ export class UmbContentWorkspaceDataManager< //#repository; #variantScaffold?: ModelVariantType; - constructor(host: UmbControllerHost, variantScaffold: ModelVariantType) { + constructor(host: UmbControllerHost, variantScaffold?: ModelVariantType) { super(host); this.#variantScaffold = variantScaffold; } + /** + * Sets the variant scaffold data + * @param {ModelVariantType} variantScaffold The variant scaffold data + * @memberof UmbContentWorkspaceDataManager + */ + setVariantScaffold(variantScaffold: ModelVariantType) { + this.#variantScaffold = variantScaffold; + } + ensureVariantData(variantId: UmbVariantId) { this.updateVariantData(variantId); } diff --git a/src/packages/core/content/manager/element-data-manager.ts b/src/packages/core/content/manager/element-data-manager.ts index 7dd3ddc91e..dfac318c5d 100644 --- a/src/packages/core/content/manager/element-data-manager.ts +++ b/src/packages/core/content/manager/element-data-manager.ts @@ -1,5 +1,5 @@ import { UmbMergeContentVariantDataController } from '../controller/merge-content-variant-data.controller.js'; -import type { UmbElementDetailModel } from '@umbraco-cms/backoffice/content'; +import type { UmbElementDetailModel } from '../types.js'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import { UmbEntityWorkspaceDataManager, type UmbWorkspaceDataManager } from '@umbraco-cms/backoffice/workspace'; diff --git a/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts b/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts index 35a1a81a5b..bf9657c99a 100644 --- a/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts +++ b/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts @@ -215,7 +215,6 @@ export abstract class UmbElementPropertyDatasetContext< override destroy() { super.destroy(); - this.#propertyVariantIdMap?.destroy(); (this.#propertyVariantIdMap as unknown) = undefined; } diff --git a/src/packages/core/content/types.ts b/src/packages/core/content/types.ts index a1e7c8cd01..169c1d43b0 100644 --- a/src/packages/core/content/types.ts +++ b/src/packages/core/content/types.ts @@ -19,10 +19,11 @@ export interface UmbPotentialContentValueModel extends UmbP segment?: string | null; } -export interface UmbContentDetailModel extends UmbElementDetailModel { +export interface UmbContentDetailModel + extends UmbElementDetailModel { unique: string; entityType: string; - variants: Array; + variants: Array; } export interface UmbContentLikeDetailModel diff --git a/src/packages/core/content/variant-picker/index.ts b/src/packages/core/content/variant-picker/index.ts new file mode 100644 index 0000000000..d4702960d5 --- /dev/null +++ b/src/packages/core/content/variant-picker/index.ts @@ -0,0 +1 @@ +export * from './types.js'; diff --git a/src/packages/core/content/variant-picker/types.ts b/src/packages/core/content/variant-picker/types.ts new file mode 100644 index 0000000000..42a4467c92 --- /dev/null +++ b/src/packages/core/content/variant-picker/types.ts @@ -0,0 +1,8 @@ +export interface UmbContentVariantPickerData { + options: Array; + pickableFilter?: (variantOption: VariantOptionModelType) => boolean; +} + +export interface UmbContentVariantPickerValue { + selection: Array; +} diff --git a/src/packages/core/content/workspace/content-detail-workspace-base.ts b/src/packages/core/content/workspace/content-detail-workspace-base.ts new file mode 100644 index 0000000000..3ae802cc25 --- /dev/null +++ b/src/packages/core/content/workspace/content-detail-workspace-base.ts @@ -0,0 +1,621 @@ +import type { UmbContentDetailModel, UmbElementValueModel } from '../types.js'; +import { UmbContentWorkspaceDataManager } from '../manager/index.js'; +import { UmbMergeContentVariantDataController } from '../controller/merge-content-variant-data.controller.js'; +import type { UmbContentVariantPickerData, UmbContentVariantPickerValue } from '../variant-picker/index.js'; +import type { UmbContentPropertyDatasetContext } from '../property-dataset-context/index.js'; +import type { UmbContentWorkspaceContext } from './content-workspace-context.interface.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbDetailRepository, UmbDetailRepositoryConstructor } from '@umbraco-cms/backoffice/repository'; +import { + UmbEntityDetailWorkspaceContextBase, + UmbWorkspaceSplitViewManager, + type UmbEntityDetailWorkspaceContextArgs, + type UmbEntityDetailWorkspaceContextCreateArgs, +} from '@umbraco-cms/backoffice/workspace'; +import { + UmbContentTypeStructureManager, + type UmbContentTypeModel, + type UmbPropertyTypeModel, +} from '@umbraco-cms/backoffice/content-type'; +import { + UMB_INVARIANT_CULTURE, + UmbVariantId, + type UmbEntityVariantModel, + type UmbEntityVariantOptionModel, +} from '@umbraco-cms/backoffice/variant'; +import { UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; +import { UmbDataTypeItemRepositoryManager } from '@umbraco-cms/backoffice/data-type'; +import { appendToFrozenArray, mergeObservables, UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; +import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; +import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; +import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; +import { + UMB_VALIDATION_CONTEXT, + UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, + UmbDataPathVariantQuery, + UmbValidationContext, + UmbVariantsValidationPathTranslator, + UmbVariantValuesValidationPathTranslator, +} from '@umbraco-cms/backoffice/validation'; +import type { UmbModalToken } from '@umbraco-cms/backoffice/modal'; +import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; +import { + UmbRequestReloadChildrenOfEntityEvent, + UmbRequestReloadStructureForEntityEvent, +} from '@umbraco-cms/backoffice/entity-action'; + +export interface UmbContentDetailWorkspaceContextArgs< + DetailModelType extends UmbContentDetailModel, + ContentTypeDetailModelType extends UmbContentTypeModel = UmbContentTypeModel, + VariantModelType extends UmbEntityVariantModel = DetailModelType extends { variants: UmbEntityVariantModel[] } + ? DetailModelType['variants'][0] + : never, + VariantOptionModelType extends UmbEntityVariantOptionModel = UmbEntityVariantOptionModel, +> extends UmbEntityDetailWorkspaceContextArgs { + contentTypeDetailRepository: UmbDetailRepositoryConstructor; + contentVariantScaffold: VariantModelType; + saveModalToken?: UmbModalToken, UmbContentVariantPickerValue>; +} + +export abstract class UmbContentDetailWorkspaceContextBase< + DetailModelType extends UmbContentDetailModel, + DetailRepositoryType extends UmbDetailRepository = UmbDetailRepository, + ContentTypeDetailModelType extends UmbContentTypeModel = UmbContentTypeModel, + VariantModelType extends UmbEntityVariantModel = DetailModelType extends { variants: UmbEntityVariantModel[] } + ? DetailModelType['variants'][0] + : never, + VariantOptionModelType extends UmbEntityVariantOptionModel = UmbEntityVariantOptionModel, + CreateArgsType extends + UmbEntityDetailWorkspaceContextCreateArgs = UmbEntityDetailWorkspaceContextCreateArgs, + > + extends UmbEntityDetailWorkspaceContextBase + implements UmbContentWorkspaceContext +{ + public readonly IS_CONTENT_WORKSPACE_CONTEXT = true as const; + + public readonly readOnlyState = new UmbReadOnlyVariantStateManager(this); + + /* Content Data */ + protected override readonly _data = new UmbContentWorkspaceDataManager(this); + public override readonly entityType = this._data.createObservablePartOfCurrent((data) => data?.entityType); + public override readonly unique = this._data.createObservablePartOfCurrent((data) => data?.unique); + public readonly values = this._data.createObservablePartOfCurrent((data) => data?.values); + public readonly variants = this._data.createObservablePartOfCurrent((data) => data?.variants ?? []); + + /* Content Type (Structure) Data */ + public readonly structure; + public readonly variesByCulture; + public readonly variesBySegment; + public readonly varies; + + abstract readonly contentTypeUnique: Observable; + + /* Data Type */ + readonly #dataTypeItemManager = new UmbDataTypeItemRepositoryManager(this); + /** + * Data Type Schema Map is used for lookup, this should make coder simpler and give better performance. [NL] + */ + #dataTypeSchemaAliasMap = new Map(); + + #varies?: boolean; + #variesByCulture?: boolean; + #variesBySegment?: boolean; + + /* Split View */ + readonly splitView = new UmbWorkspaceSplitViewManager(); + + /* Variant Options */ + // TODO: Optimize this so it uses either a App Language Context? [NL] + #languageRepository = new UmbLanguageCollectionRepository(this); + #languages = new UmbArrayState([], (x) => x.unique); + /** + * @private + * @description - Should not be used by external code. + */ + public readonly languages = this.#languages.asObservable(); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // TODO: fix type error + public readonly variantOptions; + + #saveModalToken?: UmbModalToken, UmbContentVariantPickerValue>; + + constructor( + host: UmbControllerHost, + args: UmbContentDetailWorkspaceContextArgs< + DetailModelType, + ContentTypeDetailModelType, + VariantModelType, + VariantOptionModelType + >, + ) { + super(host, args); + + this._data.setVariantScaffold(args.contentVariantScaffold); + this.#saveModalToken = args.saveModalToken; + + const contentTypeDetailRepository = new args.contentTypeDetailRepository(this); + this.structure = new UmbContentTypeStructureManager(this, contentTypeDetailRepository); + this.variesByCulture = this.structure.ownerContentTypeObservablePart((x) => x?.variesByCulture); + this.variesBySegment = this.structure.ownerContentTypeObservablePart((x) => x?.variesBySegment); + this.varies = this.structure.ownerContentTypeObservablePart((x) => + x ? x.variesByCulture || x.variesBySegment : undefined, + ); + + this.variantOptions = mergeObservables( + [this.varies, this.variants, this.languages], + ([varies, variants, languages]) => { + // TODO: When including segments, when be aware about the case of segment varying when not culture varying. [NL] + if (varies === true) { + return languages.map((language) => { + return { + variant: variants.find((x) => x.culture === language.unique), + language, + // TODO: When including segments, this object should be updated to include a object for the segment. [NL] + // TODO: When including segments, the unique should be updated to include the segment as well. [NL] + unique: language.unique, // This must be a variantId string! + culture: language.unique, + segment: null, + } as VariantOptionModelType; + }); + } else if (varies === false) { + return [ + { + variant: variants.find((x) => x.culture === null), + language: languages.find((x) => x.isDefault), + culture: null, + segment: null, + unique: UMB_INVARIANT_CULTURE, // This must be a variantId string! + } as VariantOptionModelType, + ]; + } + return [] as Array; + }, + ); + + this.addValidationContext(new UmbValidationContext(this)); + new UmbVariantValuesValidationPathTranslator(this); + new UmbVariantsValidationPathTranslator(this); + + this.observe( + this.varies, + (varies) => { + this._data.setVaries(varies); + this.#varies = varies; + }, + null, + ); + this.observe( + this.variesByCulture, + (varies) => { + this._data.setVariesByCulture(varies); + this.#variesByCulture = varies; + }, + null, + ); + this.observe( + this.variesBySegment, + (varies) => { + this._data.setVariesBySegment(varies); + this.#variesBySegment = varies; + }, + null, + ); + this.observe( + this.structure.contentTypeDataTypeUniques, + (dataTypeUniques: Array) => { + this.#dataTypeItemManager.setUniques(dataTypeUniques); + }, + null, + ); + this.observe( + this.#dataTypeItemManager.items, + (dataTypes) => { + // Make a map of the data type unique and editorAlias + this.#dataTypeSchemaAliasMap = new Map( + dataTypes.map((dataType) => { + return [dataType.unique, dataType.propertyEditorSchemaAlias]; + }), + ); + }, + null, + ); + + this.loadLanguages(); + } + + public async loadLanguages() { + // TODO: If we don't end up having a Global Context for languages, then we should at least change this into using a asObservable which should be returned from the repository. [Nl] + const { data } = await this.#languageRepository.requestCollection({}); + this.#languages.setValue(data?.items ?? []); + } + + /** + * Get the name of a variant + * @param {UmbVariantId } [variantId] - The variant id + * @returns { string | undefined} - The name of the variant + * @memberof UmbContentDetailWorkspaceContextBase + */ + public getName(variantId?: UmbVariantId): string | undefined { + const variants = this._data.getCurrent()?.variants; + if (!variants) return; + if (variantId) { + return variants.find((x) => variantId.compare(x))?.name; + } else { + return variants[0]?.name; + } + } + + /** + * Set the name of a variant + * @param {string} name - The name of the variant + * @param {UmbVariantId} [variantId] - The variant id + * @memberof UmbContentDetailWorkspaceContextBase + */ + public setName(name: string, variantId?: UmbVariantId): void { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // TODO: fix type error + this._data.updateVariantData(variantId ?? UmbVariantId.CreateInvariant(), { name }); + } + + /** + * Get an observable for the name of a variant + * @param {UmbVariantId} [variantId] - The variant id + * @returns {Observable} - The name of the variant + * @memberof UmbContentDetailWorkspaceContextBase + */ + public name(variantId?: UmbVariantId): Observable { + return this._data.createObservablePartOfCurrent( + (data) => data?.variants?.find((x) => variantId?.compare(x))?.name ?? '', + ); + } + + /* Variants */ + + /** + * Get whether the content varies by culture + * @returns { boolean | undefined } - If the content varies by culture + * @memberof UmbContentDetailWorkspaceContextBase + */ + public getVariesByCulture(): boolean | undefined { + return this.#variesByCulture; + } + + /** + * Get whether the content varies by segment + * @returns {boolean | undefined} - If the content varies by segment + * @memberof UmbContentDetailWorkspaceContextBase + */ + public getVariesBySegment(): boolean | undefined { + return this.#variesBySegment; + } + + /** + * Get whether the content varies + * @returns { boolean | undefined } - If the content varies + * @memberof UmbContentDetailWorkspaceContextBase + */ + public getVaries(): boolean | undefined { + return this.#varies; + } + + /** + * Get the variant by the given variantId + * @param {UmbVariantId} variantId - The variant id + * @returns { Observable } - The variant or undefined if not found + * @memberof UmbContentDetailWorkspaceContextBase + */ + public variantById(variantId: UmbVariantId): Observable { + return this._data.createObservablePartOfCurrent((data) => data?.variants?.find((x) => variantId.compare(x))); + } + + /** + * Get the variant by the given variantId + * @param {UmbVariantId} variantId - The variant id + * @returns { VariantModelType | undefined } - The variant or undefined if not found + * @memberof UmbContentDetailWorkspaceContextBase + */ + public getVariant(variantId: UmbVariantId): VariantModelType | undefined { + return this._data.getCurrent()?.variants?.find((x) => variantId.compare(x)); + } + + /** + * Observe the property type + * @param {string} propertyId - The id of the property + * @returns {Promise>} - An observable for the property type + * @memberof UmbContentDetailWorkspaceContextBase + */ + public async propertyStructureById(propertyId: string): Promise> { + return this.structure.propertyStructureById(propertyId); + } + + /* Values */ + + /** + * Get the values of the content + * @returns {Array | undefined} - The values of the content + * @memberof UmbContentDetailWorkspaceContextBase + */ + public getValues(): Array | undefined { + return this._data.getCurrent()?.values; + } + + /** + * @function propertyValueByAlias + * @param {string} propertyAlias - The alias of the property + * @param {UmbVariantId} variantId - The variant + * @returns {Promise | undefined>} - An observable for the value of the property + * @description Get an Observable for the value of this property. + */ + public async propertyValueByAlias( + propertyAlias: string, + variantId?: UmbVariantId, + ): Promise | undefined> { + return this._data.createObservablePartOfCurrent( + (data) => + data?.values?.find((x) => x?.alias === propertyAlias && (variantId ? variantId.compare(x) : true)) + ?.value as PropertyValueType, + ); + } + + /** + * Get the current value of the property with the given alias and variantId. + * @param {string} alias - The alias of the property + * @param {UmbVariantId | undefined} variantId - The variant id of the property + * @returns {ReturnType | undefined} The value or undefined if not set or found. + */ + public getPropertyValue(alias: string, variantId?: UmbVariantId) { + const currentData = this._data.getCurrent(); + if (currentData) { + const newDataSet = currentData.values?.find( + (x) => x.alias === alias && (variantId ? variantId.compare(x) : true), + ); + return newDataSet?.value as ReturnType; + } + return undefined; + } + + /** + * Set the value of the property with the given alias and variantId. + * @template ValueType + * @param {string} alias - The alias of the property + * @param {ValueType} value - The value to set + * @param {UmbVariantId} [variantId] - The variant id of the property + * @memberof UmbContentDetailWorkspaceContextBase + */ + public async setPropertyValue(alias: string, value: ValueType, variantId?: UmbVariantId) { + this.initiatePropertyValueChange(); + variantId ??= UmbVariantId.CreateInvariant(); + const property = await this.structure.getPropertyStructureByAlias(alias); + + if (!property) { + throw new Error(`Property alias "${alias}" not found.`); + } + + const editorAlias = this.#dataTypeSchemaAliasMap.get(property.dataType.unique); + if (!editorAlias) { + throw new Error(`Editor Alias of "${property.dataType.unique}" not found.`); + } + + // Notice the order of the properties is important for our JSON String Compare function. [NL] + const entry = { editorAlias, alias, ...variantId.toObject(), value } as UmbElementValueModel; + + const currentData = this.getData(); + if (currentData) { + const values = appendToFrozenArray( + currentData.values ?? [], + entry, + (x) => x.alias === alias && variantId!.compare(x), + ); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // TODO: fix type error + this._data.updateCurrent({ values }); + + // TODO: Ideally we should move this type of logic to the act of saving [NL] + this._data.ensureVariantData(variantId); + } + this.finishPropertyValueChange(); + } + + public initiatePropertyValueChange() { + this._data.initiatePropertyValueChange(); + } + + public finishPropertyValueChange = () => { + this._data.finishPropertyValueChange(); + }; + + protected async _determineVariantOptions() { + const options = await firstValueFrom(this.variantOptions); + + const activeVariants = this.splitView.getActiveVariants(); + const activeVariantIds = activeVariants.map((activeVariant) => UmbVariantId.Create(activeVariant)); + const changedVariantIds = this._data.getChangedVariants(); + const selectedVariantIds = activeVariantIds.concat(changedVariantIds); + + // Selected can contain entries that are not part of the options, therefor the modal filters selection based on options. + const readOnlyCultures = this.readOnlyState.getStates().map((s) => s.variantId.culture); + let selected = selectedVariantIds.map((x) => x.toString()).filter((v, i, a) => a.indexOf(v) === i); + selected = selected.filter((x) => readOnlyCultures.includes(x) === false); + + return { + options, + selected, + }; + } + + protected _readOnlyLanguageVariantsFilter = (option: VariantOptionModelType) => { + const readOnlyCultures = this.readOnlyState.getStates().map((s) => s.variantId.culture); + return readOnlyCultures.includes(option.culture) === false; + }; + + /* validation */ + protected async _runMandatoryValidationForSaveData(saveData: DetailModelType) { + // Check that the data is valid before we save it. + // Check variants have a name: + const variantsWithoutAName = saveData.variants.filter((x) => !x.name); + if (variantsWithoutAName.length > 0) { + const validationContext = await this.getContext(UMB_VALIDATION_CONTEXT); + variantsWithoutAName.forEach((variant) => { + validationContext.messages.addMessage( + 'client', + `$.variants[${UmbDataPathVariantQuery(variant)}].name`, + UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, + ); + }); + throw new Error('All variants must have a name'); + } + } + + /** + * Request a submit of the workspace, in the case of Content Workspaces the validation does not need to be valid for this to be submitted. + * @returns {Promise} a promise which resolves once it has been completed. + */ + public override requestSubmit() { + return this.#handleSubmit(); + } + + public override submit() { + return this.#handleSubmit(); + } + + // Because we do not make validation prevent submission this also submits the workspace. [NL] + public override invalidSubmit() { + return this.#handleSubmit(); + } + + async #handleSubmit() { + const data = this.getData(); + if (!data) { + throw new Error('Data is missing'); + } + + const { options, selected } = await this._determineVariantOptions(); + + let variantIds: Array = []; + + // If there is only one variant, we don't need to open the modal. + if (options.length === 0) { + throw new Error('No variants are available'); + } else if (options.length === 1) { + // If only one option we will skip ahead and save the content with the only variant available: + variantIds.push(UmbVariantId.Create(options[0])); + } else if (this.#saveModalToken) { + // If there are multiple variants, we will open the modal to let the user pick which variants to save. + const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + const result = await modalManagerContext + .open(this, this.#saveModalToken, { + data: { + options, + pickableFilter: this._readOnlyLanguageVariantsFilter, + }, + value: { selection: selected }, + }) + .onSubmit() + .catch(() => undefined); + + if (!result?.selection.length) return; + + variantIds = result?.selection.map((x) => UmbVariantId.FromString(x)) ?? []; + } else { + throw new Error('No variant picker modal token is set. There are multiple variants to save. Cannot proceed.'); + } + + const saveData = await this._data.constructData(variantIds); + await this._runMandatoryValidationForSaveData(saveData); + await this._performCreateOrUpdate(variantIds, saveData); + } + + protected async _performCreateOrUpdate(variantIds: Array, saveData: DetailModelType) { + if (this.getIsNew()) { + await this.#create(variantIds, saveData); + } else { + await this.#update(variantIds, saveData); + } + } + + async #create(variantIds: Array, saveData: DetailModelType) { + if (!this._detailRepository) throw new Error('Detail repository is not set'); + + const parent = this.getParent(); + if (!parent) throw new Error('Parent is not set'); + + const { data, error } = await this._detailRepository.create(saveData, parent.unique); + if (!data || error) { + throw new Error('Error creating content'); + } + + this._data.setPersisted(data); + + const currentData = this._data.getCurrent(); + + const variantIdsIncludingInvariant = [...variantIds, UmbVariantId.CreateInvariant()]; + + // Retrieve a data set which only contains updates from the selected variants + invariant. [NL] + const newCurrentData = await new UmbMergeContentVariantDataController(this).process( + currentData, + data, + variantIds, + variantIdsIncludingInvariant, + ); + + this._data.setCurrent(newCurrentData); + + const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); + const event = new UmbRequestReloadChildrenOfEntityEvent({ + entityType: parent.entityType, + unique: parent.unique, + }); + eventContext.dispatchEvent(event); + this.setIsNew(false); + } + + async #update(variantIds: Array, saveData: DetailModelType) { + if (!this._detailRepository) throw new Error('Detail repository is not set'); + + const { data, error } = await this._detailRepository.save(saveData); + if (!data || error) { + throw new Error('Error saving content'); + } + + this._data.setPersisted(data); + // TODO: Only update the variants that was chosen to be saved: + const currentData = this._data.getCurrent(); + + const variantIdsIncludingInvariant = [...variantIds, UmbVariantId.CreateInvariant()]; + + const newCurrentData = await new UmbMergeContentVariantDataController(this).process( + currentData, + data, + variantIds, + variantIdsIncludingInvariant, + ); + this._data.setCurrent(newCurrentData); + + const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); + const event = new UmbRequestReloadStructureForEntityEvent({ + entityType: this.getEntityType(), + unique: this.getUnique()!, + }); + + eventContext.dispatchEvent(event); + } + + abstract getContentTypeUnique(): string | undefined; + + abstract createPropertyDatasetContext( + host: UmbControllerHost, + variantId: UmbVariantId, + ): UmbContentPropertyDatasetContext; + + public override destroy(): void { + this.structure.destroy(); + this.#languageRepository.destroy(); + super.destroy(); + } +} diff --git a/src/packages/core/content/workspace/content-workspace-context.interface.ts b/src/packages/core/content/workspace/content-workspace-context.interface.ts index ecaeca41a7..ba1b17c59c 100644 --- a/src/packages/core/content/workspace/content-workspace-context.interface.ts +++ b/src/packages/core/content/workspace/content-workspace-context.interface.ts @@ -1,4 +1,5 @@ -import type { UmbContentDetailModel, UmbElementPropertyDataOwner } from '@umbraco-cms/backoffice/content'; +import type { UmbContentDetailModel } from '../types.js'; +import type { UmbElementPropertyDataOwner } from '../property-dataset-context/index.js'; import type { UmbContentTypeModel } from '@umbraco-cms/backoffice/content-type'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import type { UmbVariantId, UmbEntityVariantModel } from '@umbraco-cms/backoffice/variant'; diff --git a/src/packages/core/content/workspace/index.ts b/src/packages/core/content/workspace/index.ts index 4c72bcced7..2d6e425ec3 100644 --- a/src/packages/core/content/workspace/index.ts +++ b/src/packages/core/content/workspace/index.ts @@ -1,3 +1,4 @@ -export type * from './content-workspace-context.interface.js'; +export * from './content-detail-workspace-base.js'; export * from './content-workspace.context-token.js'; export * from './views/edit/index.js'; +export type * from './content-workspace-context.interface.js'; diff --git a/src/packages/core/repository/detail/detail-repository.interface.ts b/src/packages/core/repository/detail/detail-repository.interface.ts index d2d4832fa0..6fe7f3a281 100644 --- a/src/packages/core/repository/detail/detail-repository.interface.ts +++ b/src/packages/core/repository/detail/detail-repository.interface.ts @@ -1,7 +1,12 @@ import type { UmbRepositoryErrorResponse, UmbRepositoryResponse } from '../types.js'; import type { UmbReadDetailRepository } from './read/index.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; +export interface UmbDetailRepositoryConstructor { + new (host: UmbControllerHost): UmbDetailRepository; +} + export interface UmbDetailRepository extends UmbReadDetailRepository, UmbApi { createScaffold(preset?: Partial): Promise>; create(data: DetailModelType, parentUnique: string | null): Promise>; diff --git a/src/packages/core/repository/detail/index.ts b/src/packages/core/repository/detail/index.ts index f190db5763..3e13f2e154 100644 --- a/src/packages/core/repository/detail/index.ts +++ b/src/packages/core/repository/detail/index.ts @@ -9,3 +9,5 @@ export type { UmbReadDetailRepository } from './read/read-detail-repository.inte export type { UmbDetailDataSource, UmbDetailDataSourceConstructor } from './detail-data-source.interface.js'; export { UmbDetailRepositoryBase } from './detail-repository-base.js'; export type { UmbDetailRepository } from './detail-repository.interface.js'; + +export * from './detail-repository.interface.js'; diff --git a/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts b/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts index e2951a2b78..5410e8caa0 100644 --- a/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts +++ b/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts @@ -12,7 +12,6 @@ const observeSymbol = Symbol(); * This controller will add a custom error to the form control if the validation context has any messages for the specified data path. */ export class UmbBindServerValidationToFormControl extends UmbControllerBase { - #context?: typeof UMB_VALIDATION_CONTEXT.TYPE; #control: UmbFormControlMixinInterface; @@ -41,7 +40,7 @@ export class UmbBindServerValidationToFormControl extends UmbControllerBase { } constructor(host: UmbControllerHost, formControl: UmbFormControlMixinInterface, dataPath: string) { - super(host,'umbFormControlValidation_'+simpleHashCode(dataPath)); + super(host, 'umbFormControlValidation_' + simpleHashCode(dataPath)); this.#control = formControl; this.consumeContext(UMB_VALIDATION_CONTEXT, (context) => { this.#context = context; diff --git a/src/packages/core/workspace/components/workspace-split-view/index.ts b/src/packages/core/workspace/components/workspace-split-view/index.ts index 1312849754..509b219054 100644 --- a/src/packages/core/workspace/components/workspace-split-view/index.ts +++ b/src/packages/core/workspace/components/workspace-split-view/index.ts @@ -1,2 +1,3 @@ export * from './workspace-split-view.context.js'; export * from './workspace-split-view.element.js'; +export * from './workspace-split-view-variant-selector.element.js'; diff --git a/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts b/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts index f32f98bc68..efbce1d52c 100644 --- a/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts +++ b/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts @@ -5,29 +5,43 @@ import { UUIInputEvent, type UUIPopoverContainerElement, } from '@umbraco-cms/backoffice/external/uui'; -import { css, html, nothing, customElement, state, query, ifDefined } from '@umbraco-cms/backoffice/external/lit'; -import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; -import type { UmbDocumentVariantOptionModel, UmbDocumentWorkspaceContext } from '@umbraco-cms/backoffice/document'; -import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { + css, + html, + nothing, + customElement, + state, + query, + ifDefined, + type TemplateResult, +} from '@umbraco-cms/backoffice/external/lit'; +import { + UmbVariantId, + type UmbEntityVariantModel, + type UmbEntityVariantOptionModel, +} from '@umbraco-cms/backoffice/variant'; import { UMB_PROPERTY_DATASET_CONTEXT, isNameablePropertyDatasetContext } from '@umbraco-cms/backoffice/property'; import { UmbLitElement, umbFocus } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UmbVariantState } from '@umbraco-cms/backoffice/utils'; import { UmbDataPathVariantQuery, umbBindToValidation } from '@umbraco-cms/backoffice/validation'; +import type { UmbContentWorkspaceContext } from '@umbraco-cms/backoffice/content'; const elementName = 'umb-workspace-split-view-variant-selector'; @customElement(elementName) -export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { +export class UmbWorkspaceSplitViewVariantSelectorElement< + VariantOptionModelType extends + UmbEntityVariantOptionModel = UmbEntityVariantOptionModel, +> extends UmbLitElement { @query('#variant-selector-popover') private _popoverElement?: UUIPopoverContainerElement; @state() - private _variantOptions: Array = []; + private _variantOptions: Array = []; @state() private _readOnlyStates: Array = []; - // TODO: Stop using document context specific ActiveVariant type. @state() _activeVariants: Array = []; @@ -41,7 +55,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { private _name?: string; @state() - private _activeVariant?: UmbDocumentVariantOptionModel; + private _activeVariant?: VariantOptionModelType; @state() private _variantId?: UmbVariantId; @@ -52,11 +66,9 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { @state() private _readOnlyCultures: string[] = []; - #publishStateLocalizationMap = { - [DocumentVariantStateModel.DRAFT]: 'content_unpublished', - [DocumentVariantStateModel.PUBLISHED]: 'content_published', - [DocumentVariantStateModel.PUBLISHED_PENDING_CHANGES]: 'content_publishedPendingChanges', - [DocumentVariantStateModel.NOT_CREATED]: 'content_notCreated', + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected _variantSorter = (a: VariantOptionModelType, b: VariantOptionModelType) => { + return 0; }; constructor() { @@ -65,9 +77,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { this.consumeContext(UMB_WORKSPACE_SPLIT_VIEW_CONTEXT, (instance) => { this.#splitViewContext = instance; - // NOTICE: This is hacky (the TypeScript casting), we can only accept doing this so far because we currently only use the Variant Selector on Document Workspace. [NL] - // This would need a refactor to enable the code below to work with different ContentTypes. Main problem here is the state, which is not generic for them all. [NL] - const workspaceContext = this.#splitViewContext.getWorkspaceContext() as unknown as UmbDocumentWorkspaceContext; + const workspaceContext = this.#splitViewContext.getWorkspaceContext() as unknown as UmbContentWorkspaceContext; if (!workspaceContext) throw new Error('Split View Workspace context not found'); this.#observeVariants(workspaceContext); @@ -83,18 +93,18 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { }); } - async #observeVariants(workspaceContext: UmbDocumentWorkspaceContext) { + async #observeVariants(workspaceContext: UmbContentWorkspaceContext) { this.observe( workspaceContext.variantOptions, (variantOptions) => { - this._variantOptions = variantOptions; + this._variantOptions = (variantOptions as Array).sort(this._variantSorter); this.#setReadOnlyCultures(); }, '_observeVariantOptions', ); } - async #observeReadOnlyStates(workspaceContext: UmbDocumentWorkspaceContext) { + async #observeReadOnlyStates(workspaceContext: UmbContentWorkspaceContext) { this.observe( workspaceContext.readOnlyState.states, (states) => { @@ -105,7 +115,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { ); } - async #observeActiveVariants(workspaceContext: UmbDocumentWorkspaceContext) { + async #observeActiveVariants(workspaceContext: UmbContentWorkspaceContext) { this.observe( workspaceContext.splitView.activeVariantsInfo, (activeVariants) => { @@ -131,7 +141,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { async #observeCurrentVariant() { if (!this.#datasetContext || !this.#splitViewContext) return; - const workspaceContext = this.#splitViewContext.getWorkspaceContext() as unknown as UmbDocumentWorkspaceContext; + const workspaceContext = this.#splitViewContext.getWorkspaceContext() as unknown as UmbContentWorkspaceContext; if (!workspaceContext) return; this._variantId = this.#datasetContext.getVariantId(); @@ -140,7 +150,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { workspaceContext.variantOptions, (options) => { const option = options.find((option) => option.language.unique === this._variantId?.culture); - this._activeVariant = option; + this._activeVariant = option as VariantOptionModelType; }, '_currentLanguage', ); @@ -160,11 +170,11 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { } } - #switchVariant(variant: UmbDocumentVariantOptionModel) { + #switchVariant(variant: VariantOptionModelType) { this.#splitViewContext?.switchVariant(UmbVariantId.Create(variant)); } - #openSplitView(variant: UmbDocumentVariantOptionModel) { + #openSplitView(variant: VariantOptionModelType) { this.#splitViewContext?.openSplitView(UmbVariantId.Create(variant)); } @@ -176,7 +186,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { return culture !== null ? this._activeVariantsCultures.includes(culture) : true; } - #isCreateMode(variantOption: UmbDocumentVariantOptionModel) { + #isCreateMode(variantOption: VariantOptionModelType) { return !variantOption.variant && !this.#isVariantActive(variantOption.culture); } @@ -231,7 +241,8 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { compact slot="append" popovertarget="variant-selector-popover" - title=${ifDefined(this._activeVariant?.language.name)}> + title=${ifDefined(this._activeVariant?.language.name)} + label="Select a variant"> ${this._activeVariant?.language.name} ${this.#renderReadOnlyTag(this._activeVariant?.culture)} @@ -270,7 +281,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { : nothing; } - #renderListItem(variantOption: UmbDocumentVariantOptionModel) { + #renderListItem(variantOption: VariantOptionModelType) { return html`
  • ${this.#renderSplitViewButton(variantOption)}
  • `; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected _renderVariantDetails(variantOption: VariantOptionModelType): TemplateResult { + return html``; + } + #isReadOnly(culture: string | null) { if (!culture) return false; return this._readOnlyCultures.includes(culture); @@ -312,16 +328,17 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { : nothing; } - #renderSplitViewButton(variant: UmbDocumentVariantOptionModel) { + #renderSplitViewButton(variant: VariantOptionModelType) { return html` ${this.#isVariantActive(variant.culture) ? nothing : html` this.#openSplitView(variant)}> - Split view + Open in Split view `} `; @@ -409,6 +426,32 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { background: var(--uui-palette-sand); color: var(--uui-palette-space-cadet-light); } + .variant-selector-switch-button .variant-info { + flex-grow: 1; + } + + .variant-selector-switch-button .variant-details { + color: var(--uui-color-text-alt); + font-size: 12px; + font-weight: normal; + } + .variant-selector-switch-button .variant-details { + color: var(--uui-color-text-alt); + font-size: 12px; + font-weight: normal; + } + .variant-selector-switch-button.add-mode .variant-details { + color: var(--uui-palette-dusty-grey-dark); + } + + .variant-selector-switch-button .specs-info { + color: var(--uui-color-text-alt); + font-size: 12px; + font-weight: normal; + } + .variant-selector-switch-button.add-mode .specs-info { + color: var(--uui-palette-dusty-grey-dark); + } .variant-selector-switch-button i { font-weight: normal; @@ -452,12 +495,6 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { bottom: 1px; display: none; } - - .variant-publish-state { - color: var(--uui-palette-malibu-dimmed); - font-size: 12px; - font-weight: normal; - } `, ]; } diff --git a/src/packages/core/workspace/components/workspace-split-view/workspace-split-view.element.ts b/src/packages/core/workspace/components/workspace-split-view/workspace-split-view.element.ts index 0c3209b02b..d205654112 100644 --- a/src/packages/core/workspace/components/workspace-split-view/workspace-split-view.element.ts +++ b/src/packages/core/workspace/components/workspace-split-view/workspace-split-view.element.ts @@ -1,5 +1,5 @@ import { UmbWorkspaceSplitViewContext } from './workspace-split-view.context.js'; -import { css, html, customElement, property, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, property, ifDefined, state, nothing } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -31,8 +31,15 @@ export class UmbWorkspaceSplitViewElement extends UmbLitElement { return this.splitViewContext.getSplitViewIndex()!; } + @state() + private _variantSelectorSlotHasContent = false; + splitViewContext = new UmbWorkspaceSplitViewContext(this); + #onVariantSelectorSlotChanged(e: Event) { + this._variantSelectorSlotHasContent = (e.target as HTMLSlotElement).assignedNodes({ flatten: true }).length > 0; + } + override render() { return html` - + + ${this._variantSelectorSlotHasContent + ? nothing + : html``} + + ${this.displayNavigation ? html`` : ''} @@ -67,6 +77,7 @@ export class UmbWorkspaceSplitViewElement extends UmbLitElement { #header { flex: 1 1 auto; + display: block; } `, ]; diff --git a/src/packages/core/workspace/conditions/const.ts b/src/packages/core/workspace/conditions/const.ts index 00c36c7222..8a385d17ae 100644 --- a/src/packages/core/workspace/conditions/const.ts +++ b/src/packages/core/workspace/conditions/const.ts @@ -24,4 +24,3 @@ export const UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION = UMB_WORKSPACE_ENTITY_IS_NEW * Workspace alias condition alias */ export const UMB_WORKSPACE_CONDITION_ALIAS = 'Umb.Condition.WorkspaceAlias'; - diff --git a/src/packages/core/workspace/conditions/types.ts b/src/packages/core/workspace/conditions/types.ts index e13151df82..a9a21e6ea9 100644 --- a/src/packages/core/workspace/conditions/types.ts +++ b/src/packages/core/workspace/conditions/types.ts @@ -1,21 +1,24 @@ -import type { UMB_WORKSPACE_CONDITION_ALIAS, UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS, UMB_WORKSPACE_HAS_COLLECTION_CONDITION_ALIAS } from './const.js'; +import type { + UMB_WORKSPACE_CONDITION_ALIAS, + UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS, + UMB_WORKSPACE_HAS_COLLECTION_CONDITION_ALIAS, +} from './const.js'; import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; -export interface WorkspaceAliasConditionConfig - extends UmbConditionConfigBase { - /** - * Define the workspace that this extension should be available in - * @example - * "Umb.Workspace.Document" - */ - match?: string; - /** - * Define one or more workspaces that this extension should be available in - * @example - * ["Umb.Workspace.Document", "Umb.Workspace.Media"] - */ - oneOf?: Array; - } +export interface WorkspaceAliasConditionConfig extends UmbConditionConfigBase { + /** + * Define the workspace that this extension should be available in + * @example + * "Umb.Workspace.Document" + */ + match?: string; + /** + * Define one or more workspaces that this extension should be available in + * @example + * ["Umb.Workspace.Document", "Umb.Workspace.Media"] + */ + oneOf?: Array; +} export type WorkspaceContentTypeAliasConditionConfig = UmbConditionConfigBase<'Umb.Condition.WorkspaceContentTypeAlias'> & { diff --git a/src/packages/core/workspace/conditions/workspace-alias.condition.ts b/src/packages/core/workspace/conditions/workspace-alias.condition.ts index 7e6c421ebf..71fe226054 100644 --- a/src/packages/core/workspace/conditions/workspace-alias.condition.ts +++ b/src/packages/core/workspace/conditions/workspace-alias.condition.ts @@ -1,10 +1,10 @@ import { UMB_WORKSPACE_CONTEXT } from '../workspace.context-token.js'; import type { UmbWorkspaceContext } from '../workspace-context.interface.js'; import type { WorkspaceAliasConditionConfig } from './types.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from './const.js'; import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from './const.js'; export class UmbWorkspaceAliasCondition extends UmbConditionBase diff --git a/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts b/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts index 5a3e30e821..a99d194261 100644 --- a/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts +++ b/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts @@ -2,7 +2,7 @@ import { UmbSubmittableWorkspaceContextBase } from '../submittable/index.js'; import { UmbEntityWorkspaceDataManager } from '../entity/entity-workspace-data-manager.js'; import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import type { UmbEntityModel, UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; +import { UmbEntityContext, type UmbEntityModel, type UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; import { UMB_DISCARD_CHANGES_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { @@ -11,14 +11,19 @@ import { } from '@umbraco-cms/backoffice/entity-action'; import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api'; import { umbExtensionsRegistry, type ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; -import type { UmbDetailRepository } from '@umbraco-cms/backoffice/repository'; +import type { UmbDetailRepository, UmbRepositoryResponseWithAsObservable } from '@umbraco-cms/backoffice/repository'; -export interface UmbEntityWorkspaceContextArgs { +export interface UmbEntityDetailWorkspaceContextArgs { entityType: string; workspaceAlias: string; detailRepositoryAlias: string; } +/** + * @deprecated Use UmbEntityDetailWorkspaceContextArgs instead + */ +export type UmbEntityWorkspaceContextArgs = UmbEntityDetailWorkspaceContextArgs; + export interface UmbEntityDetailWorkspaceContextCreateArgs { parent: UmbEntityModel; preset?: Partial; @@ -45,6 +50,7 @@ export abstract class UmbEntityDetailWorkspaceContextBase< protected _detailRepository?: DetailRepositoryType; + #entityContext = new UmbEntityContext(this); #entityType: string; #parent = new UmbObjectState<{ entityType: string; unique: UmbEntityUnique } | undefined>(undefined); @@ -69,19 +75,56 @@ export abstract class UmbEntityDetailWorkspaceContextBase< this.#observeRepository(args.detailRepositoryAlias); } - getEntityType() { + /** + * Get the entity type + * @returns { string } The entity type + */ + getEntityType(): string { return this.#entityType; } - getData() { + /** + * Get the current data + * @returns { DetailModelType | undefined } The entity context + */ + getData(): DetailModelType | undefined { return this._data.getCurrent(); } - getUnique() { + /** + * Get the unique + * @returns { string | undefined } The unique identifier + */ + getUnique(): UmbEntityUnique | undefined { return this._data.getCurrent()?.unique; } - async load(unique: string) { + /** + * Get the parent + * @returns { UmbEntityModel | undefined } The parent entity + */ + getParent(): UmbEntityModel | undefined { + return this.#parent.getValue(); + } + + /** + * Get the parent unique + * @returns { string | undefined } The parent unique identifier + */ + getParentUnique(): UmbEntityUnique | undefined { + return this.#parent.getValue()?.unique; + } + + getParentEntityType() { + return this.#parent.getValue()?.entityType; + } + + /** + * Load the workspace data + * @param {string} unique The unique identifier of the entity to load. + * @returns { Promise> } The data of the entity. + */ + async load(unique: string): Promise> { await this.#init; this.resetState(); this._getDataPromise = this._detailRepository!.requestByUnique(unique); @@ -90,18 +133,34 @@ export abstract class UmbEntityDetailWorkspaceContextBase< const data = response.data; if (data) { - this.setIsNew(false); + this.#entityContext.setEntityType(this.#entityType); + this.#entityContext.setUnique(unique); this._data.setPersisted(data); this._data.setCurrent(data); + this.setIsNew(false); } return response; } - public isLoaded() { + /** + * Method to check if the workspace data is loaded. + * @returns { Promise | undefined } true if the workspace data is loaded. + * @memberof UmbEntityWorkspaceContextBase + */ + public isLoaded(): Promise | undefined { return this._getDataPromise; } + /** + * Create a data scaffold + * @param {CreateArgsType} args The arguments to create the scaffold. + * @param {UmbEntityModel} args.parent The parent entity. + * @param {UmbEntityUnique} args.parent.unique The unique identifier of the parent entity. + * @param {string} args.parent.entityType The entity type of the parent entity. + * @param {Partial} args.preset The preset data. + * @returns { Promise | undefined } The data of the scaffold. + */ async createScaffold(args: CreateArgsType) { await this.#init; this.resetState(); @@ -111,12 +170,16 @@ export abstract class UmbEntityDetailWorkspaceContextBase< let { data } = await request; if (!data) return undefined; + this.#entityContext.setEntityType(this.#entityType); + this.#entityContext.setUnique(data.unique); + if (this.modalContext) { data = { ...data, ...this.modalContext.data.preset }; } this.setIsNew(true); this._data.setPersisted(data); this._data.setCurrent(data); + return data; } @@ -139,26 +202,33 @@ export abstract class UmbEntityDetailWorkspaceContextBase< } } + /** + * Deletes the entity. + * @param unique The unique identifier of the entity to delete. + */ async delete(unique: string) { await this.#init; await this._detailRepository!.delete(unique); } /** - * @description method to check if the workspace is about to navigate away. + * Check if the workspace is about to navigate away. * @protected - * @param {string} newUrl - * @returns {*} + * @param {string} newUrl The new url that the workspace is navigating to. + * @returns { boolean} true if the workspace is navigating away. * @memberof UmbEntityWorkspaceContextBase */ - protected _checkWillNavigateAway(newUrl: string) { + protected _checkWillNavigateAway(newUrl: string): boolean { return !newUrl.includes(this.routes.getActiveLocalPath()); } async #create(currentData: DetailModelType) { + if (!this._detailRepository) throw new Error('Detail repository is not set'); + const parent = this.#parent.getValue(); if (!parent) throw new Error('Parent is not set'); - const { error, data } = await this._detailRepository!.create(currentData, parent.unique); + + const { error, data } = await this._detailRepository.create(currentData, parent.unique); if (error || !data) { throw error?.message ?? 'Repository did not return data after create.'; } @@ -252,6 +322,7 @@ export abstract class UmbEntityDetailWorkspaceContextBase< public override destroy(): void { window.removeEventListener('willchangestate', this.#onWillNavigate); this._detailRepository?.destroy(); + this.#entityContext.destroy(); super.destroy(); } } diff --git a/src/packages/data-type/tree/folder/workspace/manifests.ts b/src/packages/data-type/tree/folder/workspace/manifests.ts index 78447f41ce..f0aa6a013c 100644 --- a/src/packages/data-type/tree/folder/workspace/manifests.ts +++ b/src/packages/data-type/tree/folder/workspace/manifests.ts @@ -1,7 +1,6 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DATA_TYPE_FOLDER_ENTITY_TYPE } from '../../../entity.js'; import { UMB_DATA_TYPE_FOLDER_WORKSPACE_ALIAS } from './constants.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/data-type/workspace/manifests.ts b/src/packages/data-type/workspace/manifests.ts index c83c6cb153..61c6fb095f 100644 --- a/src/packages/data-type/workspace/manifests.ts +++ b/src/packages/data-type/workspace/manifests.ts @@ -1,6 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DATA_TYPE_WORKSPACE_ALIAS } from './constants.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/dictionary/entity-action/manifests.ts b/src/packages/dictionary/entity-action/manifests.ts index 8b70942952..66c2670c34 100644 --- a/src/packages/dictionary/entity-action/manifests.ts +++ b/src/packages/dictionary/entity-action/manifests.ts @@ -8,7 +8,7 @@ export const manifests: Array = [ kind: 'default', alias: 'Umb.EntityAction.Dictionary.Create', name: 'Create Dictionary Entity Action', - weight: 600, + weight: 1200, api: () => import('./create/create.action.js'), forEntityTypes: [UMB_DICTIONARY_ENTITY_TYPE, UMB_DICTIONARY_ROOT_ENTITY_TYPE], meta: { diff --git a/src/packages/dictionary/menu-item/manifests.ts b/src/packages/dictionary/menu-item/manifests.ts index f3a87bd76a..03da8a1d40 100644 --- a/src/packages/dictionary/menu-item/manifests.ts +++ b/src/packages/dictionary/menu-item/manifests.ts @@ -1,6 +1,6 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DICTIONARY_ENTITY_TYPE } from '../entity.js'; import { UMB_DICTIONARY_TREE_ALIAS } from '../tree/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_TRANSLATION_MENU_ALIAS } from '@umbraco-cms/backoffice/translation'; export const manifests: Array = [ diff --git a/src/packages/dictionary/workspace/manifests.ts b/src/packages/dictionary/workspace/manifests.ts index a78e4a8ef3..7acd6dbcd7 100644 --- a/src/packages/dictionary/workspace/manifests.ts +++ b/src/packages/dictionary/workspace/manifests.ts @@ -1,6 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DICTIONARY_ENTITY_TYPE } from '../entity.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const UMB_DICTIONARY_WORKSPACE_ALIAS = 'Umb.Workspace.Dictionary'; diff --git a/src/packages/documents/document-blueprints/tree/folder/workspace/manifests.ts b/src/packages/documents/document-blueprints/tree/folder/workspace/manifests.ts index 52f6f4e34f..59a6f5ef40 100644 --- a/src/packages/documents/document-blueprints/tree/folder/workspace/manifests.ts +++ b/src/packages/documents/document-blueprints/tree/folder/workspace/manifests.ts @@ -1,7 +1,6 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_BLUEPRINT_FOLDER_ENTITY_TYPE } from '../../../entity.js'; import { UMB_DOCUMENT_BLUEPRINT_FOLDER_WORKSPACE_ALIAS } from './constants.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/documents/document-blueprints/workspace/document-blueprint-workspace.context.ts b/src/packages/documents/document-blueprints/workspace/document-blueprint-workspace.context.ts index 29b346209b..45f5ade7b2 100644 --- a/src/packages/documents/document-blueprints/workspace/document-blueprint-workspace.context.ts +++ b/src/packages/documents/document-blueprints/workspace/document-blueprint-workspace.context.ts @@ -1,190 +1,52 @@ import { UmbDocumentBlueprintPropertyDatasetContext } from '../property-dataset-context/document-blueprint-property-dataset-context.js'; import { UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE } from '../entity.js'; -import { UmbDocumentBlueprintDetailRepository } from '../repository/index.js'; -import type { - UmbDocumentBlueprintDetailModel, - UmbDocumentBlueprintValueModel, - UmbDocumentBlueprintVariantModel, - UmbDocumentBlueprintVariantOptionModel, -} from '../types.js'; -import { sortVariants } from '../utils.js'; +import type { UmbDocumentBlueprintDetailRepository } from '../repository/index.js'; +import { UMB_DOCUMENT_BLUEPRINT_DETAIL_REPOSITORY_ALIAS } from '../repository/index.js'; +import type { UmbDocumentBlueprintDetailModel, UmbDocumentBlueprintVariantModel } from '../types.js'; import { UMB_CREATE_DOCUMENT_BLUEPRINT_WORKSPACE_PATH_PATTERN } from '../paths.js'; import { UMB_DOCUMENT_BLUEPRINT_WORKSPACE_ALIAS } from './manifests.js'; import { - appendToFrozenArray, - mergeObservables, - UmbArrayState, - UmbObjectState, -} from '@umbraco-cms/backoffice/observable-api'; -import { - UmbSubmittableWorkspaceContextBase, UmbWorkspaceIsNewRedirectController, UmbWorkspaceIsNewRedirectControllerAlias, - UmbWorkspaceSplitViewManager, } from '@umbraco-cms/backoffice/workspace'; -import { UmbContentTypeStructureManager } from '@umbraco-cms/backoffice/content-type'; import { type UmbDocumentTypeDetailModel, UmbDocumentTypeDetailRepository, } from '@umbraco-cms/backoffice/document-type'; -import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; -import { - UmbRequestReloadChildrenOfEntityEvent, - UmbRequestReloadStructureForEntityEvent, -} from '@umbraco-cms/backoffice/entity-action'; -import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; -import { UMB_INVARIANT_CULTURE, UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import type { UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; -import { UmbContentWorkspaceDataManager, type UmbContentWorkspaceContext } from '@umbraco-cms/backoffice/content'; -import { UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; +import { UmbContentDetailWorkspaceContextBase, type UmbContentWorkspaceContext } from '@umbraco-cms/backoffice/content'; import { UMB_DOCUMENT_COLLECTION_ALIAS, UMB_DOCUMENT_DETAIL_MODEL_VARIANT_SCAFFOLD, UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN, } from '@umbraco-cms/backoffice/document'; -import { UmbDataTypeItemRepositoryManager } from '@umbraco-cms/backoffice/data-type'; -import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; -import { map } from '@umbraco-cms/backoffice/external/rxjs'; -import { UmbEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity'; -import { UMB_SETTINGS_SECTION_PATH } from '@umbraco-cms/backoffice/settings'; +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; -type EntityModel = UmbDocumentBlueprintDetailModel; +type ContentModel = UmbDocumentBlueprintDetailModel; +type ContentTypeModel = UmbDocumentTypeDetailModel; export class UmbDocumentBlueprintWorkspaceContext - extends UmbSubmittableWorkspaceContextBase - implements UmbContentWorkspaceContext + extends UmbContentDetailWorkspaceContextBase< + ContentModel, + UmbDocumentBlueprintDetailRepository, + ContentTypeModel, + UmbDocumentBlueprintVariantModel + > + implements UmbContentWorkspaceContext { - readonly IS_CONTENT_WORKSPACE_CONTEXT = true as const; - // - readonly repository = new UmbDocumentBlueprintDetailRepository(this); - - #parent = new UmbObjectState<{ entityType: string; unique: string | null } | undefined>(undefined); - readonly parentUnique = this.#parent.asObservablePart((parent) => (parent ? parent.unique : undefined)); - readonly parentEntityType = this.#parent.asObservablePart((parent) => (parent ? parent.entityType : undefined)); - - readonly #data = new UmbContentWorkspaceDataManager(this, UMB_DOCUMENT_DETAIL_MODEL_VARIANT_SCAFFOLD); - - #getDataPromise?: Promise; - // TODO: Optimize this so it uses either a App Language Context? [NL] - #languageRepository = new UmbLanguageCollectionRepository(this); - #languages = new UmbArrayState([], (x) => x.unique); - public readonly languages = this.#languages.asObservable(); - - public readonly readOnlyState = new UmbReadOnlyVariantStateManager(this); - - public isLoaded() { - return this.#getDataPromise; - } - - readonly unique = this.#data.createObservablePartOfCurrent((data) => data?.unique); - readonly entityType = this.#data.createObservablePartOfCurrent((data) => data?.entityType); - - readonly contentTypeUnique = this.#data.createObservablePartOfCurrent((data) => data?.documentType.unique); - - readonly variants = this.#data.createObservablePartOfCurrent((data) => data?.variants || []); - - readonly values = this.#data.createObservablePartOfCurrent((data) => data?.values); - getValues() { - return this.#data.getCurrent()?.values; - } - - //readonly urls = this.#data.current.asObservablePart((data) => data?.urls || []); - - readonly structure = new UmbContentTypeStructureManager(this, new UmbDocumentTypeDetailRepository(this)); - readonly variesByCulture = this.structure.ownerContentTypeObservablePart((x) => x?.variesByCulture); - readonly variesBySegment = this.structure.ownerContentTypeObservablePart((x) => x?.variesBySegment); - readonly varies = this.structure.ownerContentTypeObservablePart((x) => - x ? x.variesByCulture || x.variesBySegment : undefined, - ); - #varies?: boolean; - #variesByCulture?: boolean; - #variesBySegment?: boolean; - - readonly #dataTypeItemManager = new UmbDataTypeItemRepositoryManager(this); - #dataTypeSchemaAliasMap = new Map(); - - readonly splitView = new UmbWorkspaceSplitViewManager(); - - readonly variantOptions = mergeObservables( - [this.varies, this.variants, this.languages], - ([varies, variants, languages]) => { - // TODO: When including segments, when be aware about the case of segment varying when not culture varying. [NL] - if (varies === true) { - return languages.map((language) => { - return { - variant: variants.find((x) => x.culture === language.unique), - language, - // TODO: When including segments, this object should be updated to include a object for the segment. [NL] - // TODO: When including segments, the unique should be updated to include the segment as well. [NL] - unique: language.unique, // This must be a variantId string! - culture: language.unique, - segment: null, - } as UmbDocumentBlueprintVariantOptionModel; - }); - } else if (varies === false) { - return [ - { - variant: variants.find((x) => x.culture === null), - language: languages.find((x) => x.isDefault), - culture: null, - segment: null, - unique: UMB_INVARIANT_CULTURE, // This must be a variantId string! - } as UmbDocumentBlueprintVariantOptionModel, - ]; - } - return []; - }, - ).pipe(map((results) => results.sort(sortVariants))); - - // TODO: this should be set up for all entity workspace contexts in a base class - #entityContext = new UmbEntityContext(this); + readonly contentTypeUnique = this._data.createObservablePartOfCurrent((data) => data?.documentType.unique); constructor(host: UmbControllerHost) { - super(host, UMB_DOCUMENT_BLUEPRINT_WORKSPACE_ALIAS); - - this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique), null); - this.observe( - this.varies, - (varies) => { - this.#data.setVaries(varies); - this.#varies = varies; - }, - null, - ); - this.observe( - this.variesByCulture, - (varies) => { - this.#data.setVariesByCulture(varies); - this.#variesByCulture = varies; - }, - null, - ); - this.observe( - this.variesBySegment, - (varies) => { - this.#data.setVariesBySegment(varies); - this.#variesBySegment = varies; - }, - null, - ); - this.observe( - this.structure.contentTypeDataTypeUniques, - (dataTypeUniques: Array) => { - this.#dataTypeItemManager.setUniques(dataTypeUniques); - }, - null, - ); - this.observe(this.#dataTypeItemManager.items, (dataTypes) => { - // Make a map of the data type unique and editorAlias: - this.#dataTypeSchemaAliasMap = new Map( - dataTypes.map((dataType) => { - return [dataType.unique, dataType.propertyEditorSchemaAlias]; - }), - ); + super(host, { + entityType: UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE, + workspaceAlias: UMB_DOCUMENT_BLUEPRINT_WORKSPACE_ALIAS, + detailRepositoryAlias: UMB_DOCUMENT_BLUEPRINT_DETAIL_REPOSITORY_ALIAS, + contentTypeDetailRepository: UmbDocumentTypeDetailRepository, + contentVariantScaffold: UMB_DOCUMENT_DETAIL_MODEL_VARIANT_SCAFFOLD, }); - this.loadLanguages(); + this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique), null); this.routes.setRoutes([ { @@ -215,251 +77,39 @@ export class UmbDocumentBlueprintWorkspaceContext ]); } - override resetState() { - super.resetState(); - this.#data.clear(); - } - - async loadLanguages() { - // TODO: If we don't end up having a Global Context for languages, then we should at least change this into using a asObservable which should be returned from the repository. [Nl] - const { data } = await this.#languageRepository.requestCollection({}); - this.#languages.setValue(data?.items ?? []); - } - - async load(unique: string) { - this.resetState(); - this.#getDataPromise = this.repository.requestByUnique(unique); - type GetDataType = Awaited>; - const { data, asObservable } = (await this.#getDataPromise) as GetDataType; - - if (data) { - this.#entityContext.setEntityType(UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE); - this.#entityContext.setUnique(unique); - this.setIsNew(false); - this.#data.setPersisted(data); - this.#data.setCurrent(data); - } - - this.observe(asObservable(), (entity) => this.#onStoreChange(entity), 'UmbDocumentBlueprintStoreObserver'); - } - - #onStoreChange(entity: EntityModel | undefined) { - if (!entity) { - //TODO: This solution is alright for now. But reconsider when we introduce signal-r - history.pushState(null, '', UMB_SETTINGS_SECTION_PATH); - } - } - async create(parent: UmbEntityModel, documentTypeUnique: string) { - this.resetState(); - this.#parent.setValue(parent); - - this.#getDataPromise = this.repository.createScaffold({ - documentType: { unique: documentTypeUnique, collection: null }, + return this.createScaffold({ + parent, + preset: { + documentType: { + unique: documentTypeUnique, + collection: null, + }, + }, }); - - const { data } = await this.#getDataPromise; - if (!data) return undefined; - - this.#entityContext.setEntityType(UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE); - this.#entityContext.setUnique(data.unique); - this.setIsNew(true); - this.#data.setPersisted(undefined); - this.#data.setCurrent(data); - return data; } getCollectionAlias() { return UMB_DOCUMENT_COLLECTION_ALIAS; } - getData() { - return this.#data.getCurrent(); - } - - getUnique() { - return this.getData()?.unique; - } - - getEntityType() { - return UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE; - } - - getContentTypeId() { - return this.getData()?.documentType.unique; - } - - getVaries() { - return this.#varies; - } - getVariesByCulture() { - return this.#variesByCulture; - } - getVariesBySegment() { - return this.#variesBySegment; - } - - variantById(variantId: UmbVariantId) { - return this.#data.createObservablePartOfCurrent((data) => data?.variants?.find((x) => variantId.compare(x))); - } - - getVariant(variantId: UmbVariantId) { - return this.#data.getCurrent()?.variants?.find((x) => variantId.compare(x)); - } - - getName(variantId?: UmbVariantId) { - const variants = this.#data.getCurrent()?.variants; - if (!variants) return; - if (variantId) { - return variants.find((x) => variantId.compare(x))?.name; - } else { - return variants[0]?.name; - } - } - - setName(name: string, variantId?: UmbVariantId) { - this.#data.updateVariantData(variantId ?? UmbVariantId.CreateInvariant(), { name }); - } - - name(variantId?: UmbVariantId) { - return this.#data.createObservablePartOfCurrent( - (data) => data?.variants?.find((x) => variantId?.compare(x))?.name ?? '', - ); - } - - async propertyStructureById(propertyId: string) { - return this.structure.propertyStructureById(propertyId); - } /** - * @function propertyValueByAlias - * @param variantId - * @param {string} propertyAlias - * @returns {Promise | undefined>} - * @description Get an Observable for the value of this property. + * Gets the unique identifier of the content type. + * @deprecated Use `getContentTypeUnique` instead. + * @returns { string | undefined} The unique identifier of the content type. + * @memberof UmbDocumentWorkspaceContext */ - async propertyValueByAlias( - propertyAlias: string, - variantId?: UmbVariantId, - ): Promise | undefined> { - return this.#data.createObservablePartOfCurrent( - (data) => - data?.values?.find((x) => x?.alias === propertyAlias && (variantId ? variantId.compare(x as any) : true)) - ?.value as PropertyValueType, - ); + getContentTypeId(): string | undefined { + return this.getContentTypeUnique(); } /** - * Get the current value of the property with the given alias and variantId. - * @param alias - * @param variantId - * @returns The value or undefined if not set or found. + * Gets the unique identifier of the content type. + * @returns { string | undefined} The unique identifier of the content type. + * @memberof UmbDocumentWorkspaceContext */ - getPropertyValue(alias: string, variantId?: UmbVariantId) { - const currentData = this.#data.getCurrent(); - if (currentData) { - const newDataSet = currentData.values?.find( - (x) => x.alias === alias && (variantId ? variantId.compare(x as any) : true), - ); - return newDataSet?.value as ReturnType; - } - return undefined; - } - async setPropertyValue(alias: string, value: ValueType, variantId?: UmbVariantId) { - this.initiatePropertyValueChange(); - variantId ??= UmbVariantId.CreateInvariant(); - const property = await this.structure.getPropertyStructureByAlias(alias); - - if (!property) { - throw new Error(`Property alias "${alias}" not found.`); - } - - const editorAlias = this.#dataTypeSchemaAliasMap.get(property.dataType.unique); - if (!editorAlias) { - throw new Error(`Editor Alias of "${property.dataType.unique}" not found.`); - } - - const entry = { ...variantId.toObject(), alias, editorAlias, value } as UmbDocumentBlueprintValueModel; - - const currentData = this.getData(); - if (currentData) { - const values = appendToFrozenArray( - currentData.values ?? [], - entry, - (x) => x.alias === alias && variantId!.compare(x), - ); - this.#data.updateCurrent({ values }); - - // TODO: We should move this type of logic to the act of saving [NL] - this.#data.ensureVariantData(variantId); - } - this.finishPropertyValueChange(); - } - - initiatePropertyValueChange() { - this.#data.initiatePropertyValueChange(); - } - finishPropertyValueChange = () => { - this.#data.finishPropertyValueChange(); - }; - - async #handleSave() { - const current = this.#data.getCurrent(); - if (!current?.unique) throw new Error('Unique is missing'); - - if (this.getIsNew()) { - const parent = this.#parent.getValue(); - if (!parent) throw new Error('Parent is not set'); - - const { data, error } = await this.repository.create(current, parent.unique); - if (!data || error) { - console.error('Error creating document', error); - throw new Error('Error creating document'); - } - - this.setIsNew(false); - this.#data.setPersisted(data); - // We do not know about the variant IDs, so lets update everything. - this.#data.setCurrent(data); - - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadChildrenOfEntityEvent({ - entityType: parent.entityType, - unique: parent.unique, - }); - eventContext.dispatchEvent(event); - } else { - // Save: - const { data, error } = await this.repository.save(current); - if (!data || error) { - console.error('Error saving document', error); - throw new Error('Error saving document'); - } - - this.#data.setPersisted(data); - // We do not know about the variant IDs, so lets update everything. - this.#data.setCurrent(data); - - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadStructureForEntityEvent({ - unique: this.getUnique()!, - entityType: this.getEntityType(), - }); - - eventContext.dispatchEvent(event); - } - } - - async submit() { - const data = this.getData(); - if (!data) throw new Error('Data is missing'); - await this.#handleSave(); - } - - async delete() { - const id = this.getUnique(); - if (id) { - await this.repository.delete(id); - } + getContentTypeUnique(): string | undefined { + return this.getData()?.documentType.unique; } public createPropertyDatasetContext( @@ -468,12 +118,6 @@ export class UmbDocumentBlueprintWorkspaceContext ): UmbDocumentBlueprintPropertyDatasetContext { return new UmbDocumentBlueprintPropertyDatasetContext(host, this, variantId); } - - public override destroy(): void { - this.structure.destroy(); - this.#languageRepository.destroy(); - super.destroy(); - } } export { UmbDocumentBlueprintWorkspaceContext as api }; diff --git a/src/packages/documents/document-blueprints/workspace/manifests.ts b/src/packages/documents/document-blueprints/workspace/manifests.ts index ba2884d0e4..06ec9abd48 100644 --- a/src/packages/documents/document-blueprints/workspace/manifests.ts +++ b/src/packages/documents/document-blueprints/workspace/manifests.ts @@ -1,6 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE } from '../entity.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const UMB_DOCUMENT_BLUEPRINT_WORKSPACE_ALIAS = 'Umb.Workspace.DocumentBlueprint'; diff --git a/src/packages/documents/document-types/menu/manifests.ts b/src/packages/documents/document-types/menu/manifests.ts index 73d7506420..35ea56aca9 100644 --- a/src/packages/documents/document-types/menu/manifests.ts +++ b/src/packages/documents/document-types/menu/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_TYPE_TREE_ALIAS } from '../tree/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/documents/document-types/modals/document-type-picker-modal.token.ts b/src/packages/documents/document-types/modals/document-type-picker-modal.token.ts index 9ab4a1ac6d..ca1cac6fad 100644 --- a/src/packages/documents/document-types/modals/document-type-picker-modal.token.ts +++ b/src/packages/documents/document-types/modals/document-type-picker-modal.token.ts @@ -1,10 +1,7 @@ +import { UMB_DOCUMENT_TYPE_ENTITY_TYPE, UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE } from '../entity.js'; import { UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PATH_PATTERN } from '../paths.js'; +import type { UmbDocumentTypeTreeItemModel } from '../tree/index.js'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; -import { - UMB_DOCUMENT_TYPE_ENTITY_TYPE, - UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE, - type UmbDocumentTypeTreeItemModel, -} from '@umbraco-cms/backoffice/document-type'; import { type UmbTreePickerModalValue, type UmbTreePickerModalData, diff --git a/src/packages/documents/document-types/tree/folder/workspace/manifests.ts b/src/packages/documents/document-types/tree/folder/workspace/manifests.ts index d0a5f120c4..34dcbc13e2 100644 --- a/src/packages/documents/document-types/tree/folder/workspace/manifests.ts +++ b/src/packages/documents/document-types/tree/folder/workspace/manifests.ts @@ -1,7 +1,6 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from '../../../entity.js'; import { UMB_DOCUMENT_TYPE_FOLDER_WORKSPACE_ALIAS } from './constants.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/documents/document-types/tree/index.ts b/src/packages/documents/document-types/tree/index.ts index f2f1e85a61..a372c0b6a5 100644 --- a/src/packages/documents/document-types/tree/index.ts +++ b/src/packages/documents/document-types/tree/index.ts @@ -1,3 +1,4 @@ export { UMB_DOCUMENT_TYPE_TREE_STORE_CONTEXT } from './document-type.tree.store.context-token.js'; export { UMB_DOCUMENT_TYPE_TREE_REPOSITORY_ALIAS, UMB_DOCUMENT_TYPE_TREE_ALIAS } from './constants.js'; export * from './folder/index.js'; +export type * from './types.js'; diff --git a/src/packages/documents/document-types/workspace/manifests.ts b/src/packages/documents/document-types/workspace/manifests.ts index 028faf033a..3177e42a18 100644 --- a/src/packages/documents/document-types/workspace/manifests.ts +++ b/src/packages/documents/document-types/workspace/manifests.ts @@ -1,6 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_TYPE_COMPOSITION_REPOSITORY_ALIAS } from '../repository/composition/index.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS = 'Umb.Workspace.DocumentType'; diff --git a/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts b/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts index 29e72c693b..4b0fafd6f8 100644 --- a/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts +++ b/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts @@ -12,6 +12,7 @@ import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; import type { ManifestCollectionAction } from '@umbraco-cms/backoffice/collection'; import type { UmbAllowedDocumentTypeModel } from '@umbraco-cms/backoffice/document-type'; +import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; @customElement('umb-create-document-collection-action') export class UmbCreateDocumentCollectionActionElement extends UmbLitElement { @@ -25,7 +26,7 @@ export class UmbCreateDocumentCollectionActionElement extends UmbLitElement { private _currentView?: string; @state() - private _documentUnique?: string; + private _documentUnique?: UmbEntityUnique; @state() private _documentTypeUnique?: string; diff --git a/src/packages/documents/documents/menu/manifests.ts b/src/packages/documents/documents/menu/manifests.ts index a842fa686c..dc76417093 100644 --- a/src/packages/documents/documents/menu/manifests.ts +++ b/src/packages/documents/documents/menu/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_TREE_ALIAS } from '../tree/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_CONTENT_MENU_ALIAS = 'Umb.Menu.Content'; diff --git a/src/packages/documents/documents/modals/types.ts b/src/packages/documents/documents/modals/types.ts index d3e91cdf89..56eeb92f50 100644 --- a/src/packages/documents/documents/modals/types.ts +++ b/src/packages/documents/documents/modals/types.ts @@ -1,10 +1,5 @@ import type { UmbDocumentVariantOptionModel } from '../types.js'; +import type { UmbContentVariantPickerData, UmbContentVariantPickerValue } from '@umbraco-cms/backoffice/content'; -export interface UmbDocumentVariantPickerData { - options: Array; - pickableFilter?: (variantOption: UmbDocumentVariantOptionModel) => boolean; -} - -export interface UmbDocumentVariantPickerValue { - selection: Array; -} +export type UmbDocumentVariantPickerData = UmbContentVariantPickerData; +export type UmbDocumentVariantPickerValue = UmbContentVariantPickerValue; diff --git a/src/packages/documents/documents/rollback/entity-action/manifests.ts b/src/packages/documents/documents/rollback/entity-action/manifests.ts index 2bed9b0321..4e7d45616a 100644 --- a/src/packages/documents/documents/rollback/entity-action/manifests.ts +++ b/src/packages/documents/documents/rollback/entity-action/manifests.ts @@ -1,7 +1,7 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; import { UMB_USER_PERMISSION_DOCUMENT_ROLLBACK } from '../../user-permissions/index.js'; import { UMB_DOCUMENT_WORKSPACE_ALIAS } from '../../workspace/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin'; export const manifests: Array = [ diff --git a/src/packages/documents/documents/workspace/document-workspace-split-view-variant-selector.element.ts b/src/packages/documents/documents/workspace/document-workspace-split-view-variant-selector.element.ts new file mode 100644 index 0000000000..f08abb5f61 --- /dev/null +++ b/src/packages/documents/documents/workspace/document-workspace-split-view-variant-selector.element.ts @@ -0,0 +1,30 @@ +import type { UmbDocumentVariantOptionModel } from '../types.js'; +import { sortVariants } from '../utils.js'; +import { customElement, html } from '@umbraco-cms/backoffice/external/lit'; +import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; +import { UmbWorkspaceSplitViewVariantSelectorElement } from '@umbraco-cms/backoffice/workspace'; + +const elementName = 'umb-document-workspace-split-view-variant-selector'; +@customElement(elementName) +export class UmbDocumentWorkspaceSplitViewVariantSelectorElement extends UmbWorkspaceSplitViewVariantSelectorElement { + protected override _variantSorter = sortVariants; + + #publishStateLocalizationMap = { + [DocumentVariantStateModel.DRAFT]: 'content_unpublished', + [DocumentVariantStateModel.PUBLISHED]: 'content_published', + [DocumentVariantStateModel.PUBLISHED_PENDING_CHANGES]: 'content_publishedPendingChanges', + [DocumentVariantStateModel.NOT_CREATED]: 'content_notCreated', + }; + + override _renderVariantDetails(variantOption: UmbDocumentVariantOptionModel) { + return html` ${this.localize.term( + this.#publishStateLocalizationMap[variantOption.variant?.state || DocumentVariantStateModel.NOT_CREATED], + )}`; + } +} + +declare global { + interface HTMLElementTagNameMap { + [elementName]: UmbDocumentWorkspaceSplitViewVariantSelectorElement; + } +} diff --git a/src/packages/documents/documents/workspace/document-workspace-split-view.element.ts b/src/packages/documents/documents/workspace/document-workspace-split-view.element.ts index 97f56208fa..09c5ab3bbc 100644 --- a/src/packages/documents/documents/workspace/document-workspace-split-view.element.ts +++ b/src/packages/documents/documents/workspace/document-workspace-split-view.element.ts @@ -4,6 +4,8 @@ import { css, html, nothing, customElement, state, repeat } from '@umbraco-cms/b import type { ActiveVariant } from '@umbraco-cms/backoffice/workspace'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import './document-workspace-split-view-variant-selector.element.js'; + @customElement('umb-document-workspace-split-view') export class UmbDocumentWorkspaceSplitViewElement extends UmbLitElement { // TODO: Refactor: use the split view context token: @@ -15,7 +17,7 @@ export class UmbDocumentWorkspaceSplitViewElement extends UmbLitElement { constructor() { super(); - // TODO: Refactor: use a split view workspace context token: + // TODO: Refactor: use a split view workspace context token: [NL] this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (context) => { this._workspaceContext = context; this._observeActiveVariantInfo(); @@ -44,7 +46,10 @@ export class UmbDocumentWorkspaceSplitViewElement extends UmbLitElement { + .displayNavigation=${view.index === this._variants!.length - 1}> + + `, )} diff --git a/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/packages/documents/documents/workspace/document-workspace.context.ts index 0ca2b56303..b9ef2f99dc 100644 --- a/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -1,19 +1,14 @@ import { UmbDocumentTypeDetailRepository } from '../../document-types/repository/detail/document-type-detail.repository.js'; import { UmbDocumentPropertyDatasetContext } from '../property-dataset-context/document-property-dataset-context.js'; import { UMB_DOCUMENT_ENTITY_TYPE } from '../entity.js'; -import { UmbDocumentDetailRepository } from '../repository/index.js'; -import type { - UmbDocumentVariantPublishModel, - UmbDocumentDetailModel, - UmbDocumentValueModel, - UmbDocumentVariantModel, - UmbDocumentVariantOptionModel, -} from '../types.js'; +import type { UmbDocumentDetailRepository } from '../repository/index.js'; +import { UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS } from '../repository/index.js'; +import type { UmbDocumentVariantPublishModel, UmbDocumentDetailModel, UmbDocumentVariantModel } from '../types.js'; import { UMB_DOCUMENT_PUBLISH_MODAL, UMB_DOCUMENT_PUBLISH_WITH_DESCENDANTS_MODAL, - UMB_DOCUMENT_SCHEDULE_MODAL, UMB_DOCUMENT_SAVE_MODAL, + UMB_DOCUMENT_SCHEDULE_MODAL, } from '../modals/index.js'; import { UmbDocumentPublishingRepository } from '../repository/publishing/index.js'; import { UmbUnpublishDocumentEntityAction } from '../entity-actions/unpublish.action.js'; @@ -23,224 +18,76 @@ import { UMB_CREATE_FROM_BLUEPRINT_DOCUMENT_WORKSPACE_PATH_PATTERN, UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN, } from '../paths.js'; -import { UMB_DOCUMENTS_SECTION_PATH } from '../../section/paths.js'; import { UmbDocumentPreviewRepository } from '../repository/preview/index.js'; -import { sortVariants } from '../utils.js'; import { UMB_DOCUMENT_COLLECTION_ALIAS } from '../collection/index.js'; import { UMB_DOCUMENT_DETAIL_MODEL_VARIANT_SCAFFOLD, UMB_DOCUMENT_WORKSPACE_ALIAS } from './constants.js'; -import { UmbEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; import { UMB_INVARIANT_CULTURE, UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import { UmbContentTypeStructureManager } from '@umbraco-cms/backoffice/content-type'; import { type UmbPublishableWorkspaceContext, - UmbSubmittableWorkspaceContextBase, UmbWorkspaceIsNewRedirectController, - UmbWorkspaceSplitViewManager, UmbWorkspaceIsNewRedirectControllerAlias, } from '@umbraco-cms/backoffice/workspace'; -import { - appendToFrozenArray, - mergeObservables, - UmbArrayState, - UmbObjectState, -} from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; -import { type Observable, firstValueFrom, map } from '@umbraco-cms/backoffice/external/rxjs'; import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; import { UmbRequestReloadChildrenOfEntityEvent, UmbRequestReloadStructureForEntityEvent, } from '@umbraco-cms/backoffice/entity-action'; import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; -import { - UMB_VALIDATION_CONTEXT, - UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, - UmbDataPathVariantQuery, - UmbServerModelValidatorContext, - UmbValidationContext, - UmbVariantValuesValidationPathTranslator, - UmbVariantsValidationPathTranslator, -} from '@umbraco-cms/backoffice/validation'; +import { UmbServerModelValidatorContext } from '@umbraco-cms/backoffice/validation'; import { UmbDocumentBlueprintDetailRepository } from '@umbraco-cms/backoffice/document-blueprint'; import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; import { - UmbContentWorkspaceDataManager, - UmbMergeContentVariantDataController, + UmbContentDetailWorkspaceContextBase, type UmbContentCollectionWorkspaceContext, type UmbContentWorkspaceContext, } from '@umbraco-cms/backoffice/content'; import type { UmbDocumentTypeDetailModel } from '@umbraco-cms/backoffice/document-type'; import { UmbIsTrashedEntityContext } from '@umbraco-cms/backoffice/recycle-bin'; -import { UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; -import { UmbDataTypeItemRepositoryManager } from '@umbraco-cms/backoffice/data-type'; -import type { UmbRepositoryResponse } from '@umbraco-cms/backoffice/repository'; import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; -type EntityModel = UmbDocumentDetailModel; -type EntityTypeModel = UmbDocumentTypeDetailModel; +type ContentModel = UmbDocumentDetailModel; +type ContentTypeModel = UmbDocumentTypeDetailModel; export class UmbDocumentWorkspaceContext - extends UmbSubmittableWorkspaceContextBase + extends UmbContentDetailWorkspaceContextBase< + ContentModel, + UmbDocumentDetailRepository, + ContentTypeModel, + UmbDocumentVariantModel + > implements - UmbContentWorkspaceContext, + UmbContentWorkspaceContext, UmbPublishableWorkspaceContext, UmbContentCollectionWorkspaceContext { - public readonly IS_CONTENT_WORKSPACE_CONTEXT = true as const; - - public readonly repository = new UmbDocumentDetailRepository(this); public readonly publishingRepository = new UmbDocumentPublishingRepository(this); - #parent = new UmbObjectState(undefined); - readonly parentUnique = this.#parent.asObservablePart((parent) => (parent ? parent.unique : undefined)); - readonly parentEntityType = this.#parent.asObservablePart((parent) => (parent ? parent.entityType : undefined)); - - readonly #data = new UmbContentWorkspaceDataManager(this, UMB_DOCUMENT_DETAIL_MODEL_VARIANT_SCAFFOLD); - #getDataPromise?: Promise>; - - // TODo: Optimize this so it uses either a App Language Context? [NL] - #languageRepository = new UmbLanguageCollectionRepository(this); - #languages = new UmbArrayState([], (x) => x.unique); - /** - * @private - * @description - Should not be used by external code. - */ - public readonly languages = this.#languages.asObservable(); - #serverValidation = new UmbServerModelValidatorContext(this); #validationRepository?: UmbDocumentValidationRepository; - public readonly readOnlyState = new UmbReadOnlyVariantStateManager(this); - - public isLoaded() { - return this.#getDataPromise; - } - - readonly data = this.#data.current; - readonly unique = this.#data.createObservablePartOfCurrent((data) => data?.unique); - readonly entityType = this.#data.createObservablePartOfCurrent((data) => data?.entityType); - readonly isTrashed = this.#data.createObservablePartOfCurrent((data) => data?.isTrashed); - readonly values = this.#data.createObservablePartOfCurrent((data) => data?.values); - getValues() { - return this.#data.getCurrent()?.values; - } - - readonly contentTypeUnique = this.#data.createObservablePartOfCurrent((data) => data?.documentType.unique); - readonly contentTypeHasCollection = this.#data.createObservablePartOfCurrent( + readonly isTrashed = this._data.createObservablePartOfCurrent((data) => data?.isTrashed); + readonly contentTypeUnique = this._data.createObservablePartOfCurrent((data) => data?.documentType.unique); + readonly contentTypeHasCollection = this._data.createObservablePartOfCurrent( (data) => !!data?.documentType.collection, ); + readonly urls = this._data.createObservablePartOfCurrent((data) => data?.urls || []); + readonly templateId = this._data.createObservablePartOfCurrent((data) => data?.template?.unique || null); - readonly variants = this.#data.createObservablePartOfCurrent((data) => data?.variants ?? []); - - readonly structure = new UmbContentTypeStructureManager(this, new UmbDocumentTypeDetailRepository(this)); - readonly variesByCulture = this.structure.ownerContentTypeObservablePart((x) => x?.variesByCulture); - readonly variesBySegment = this.structure.ownerContentTypeObservablePart((x) => x?.variesBySegment); - readonly varies = this.structure.ownerContentTypeObservablePart((x) => - x ? x.variesByCulture || x.variesBySegment : undefined, - ); - #varies?: boolean; - #variesByCulture?: boolean; - #variesBySegment?: boolean; - - readonly urls = this.#data.createObservablePartOfCurrent((data) => data?.urls || []); - readonly templateId = this.#data.createObservablePartOfCurrent((data) => data?.template?.unique || null); - - readonly #dataTypeItemManager = new UmbDataTypeItemRepositoryManager(this); - #dataTypeSchemaAliasMap = new Map(); - - readonly splitView = new UmbWorkspaceSplitViewManager(); - - readonly variantOptions = mergeObservables( - [this.varies, this.variants, this.languages], - ([varies, variants, languages]) => { - // TODO: When including segments, when be aware about the case of segment varying when not culture varying. [NL] - if (varies === true) { - return languages.map((language) => { - return { - variant: variants.find((x) => x.culture === language.unique), - language, - // TODO: When including segments, this object should be updated to include a object for the segment. [NL] - // TODO: When including segments, the unique should be updated to include the segment as well. [NL] - unique: language.unique, // This must be a variantId string! - culture: language.unique, - segment: null, - } as UmbDocumentVariantOptionModel; - }); - } else if (varies === false) { - return [ - { - variant: variants.find((x) => x.culture === null), - language: languages.find((x) => x.isDefault), - culture: null, - segment: null, - unique: UMB_INVARIANT_CULTURE, // This must be a variantId string! - } as UmbDocumentVariantOptionModel, - ]; - } - return [] as Array; - }, - ).pipe(map((results) => results.sort(sortVariants))); - - // TODO: this should be set up for all entity workspace contexts in a base class - #entityContext = new UmbEntityContext(this); - // TODO: this might not be the correct place to spin this up #isTrashedContext = new UmbIsTrashedEntityContext(this); constructor(host: UmbControllerHost) { - super(host, UMB_DOCUMENT_WORKSPACE_ALIAS); - - this.addValidationContext(new UmbValidationContext(this)); - - new UmbVariantValuesValidationPathTranslator(this); - new UmbVariantsValidationPathTranslator(this); + super(host, { + entityType: UMB_DOCUMENT_ENTITY_TYPE, + workspaceAlias: UMB_DOCUMENT_WORKSPACE_ALIAS, + detailRepositoryAlias: UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS, + contentTypeDetailRepository: UmbDocumentTypeDetailRepository, + contentVariantScaffold: UMB_DOCUMENT_DETAIL_MODEL_VARIANT_SCAFFOLD, + saveModalToken: UMB_DOCUMENT_SAVE_MODAL, + }); this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique), null); - this.observe( - this.varies, - (varies) => { - this.#data.setVaries(varies); - this.#varies = varies; - }, - null, - ); - this.observe( - this.variesByCulture, - (varies) => { - this.#data.setVariesByCulture(varies); - this.#variesByCulture = varies; - }, - null, - ); - this.observe( - this.variesBySegment, - (varies) => { - this.#data.setVariesBySegment(varies); - this.#variesBySegment = varies; - }, - null, - ); - this.observe( - this.structure.contentTypeDataTypeUniques, - (dataTypeUniques: Array) => { - this.#dataTypeItemManager.setUniques(dataTypeUniques); - }, - null, - ); - this.observe( - this.#dataTypeItemManager.items, - (dataTypes) => { - // Make a map of the data type unique and editorAlias: - this.#dataTypeSchemaAliasMap = new Map( - dataTypes.map((dataType) => { - return [dataType.unique, dataType.propertyEditorSchemaAlias]; - }), - ); - }, - null, - ); - - this.loadLanguages(); this.routes.setRoutes([ { @@ -293,303 +140,74 @@ export class UmbDocumentWorkspaceContext ]); } - override resetState() { - super.resetState(); - this.#data.clear(); - this.removeUmbControllerByAlias(UmbWorkspaceIsNewRedirectControllerAlias); - } - - async loadLanguages() { - // TODO: If we don't end up having a Global Context for languages, then we should at least change this into using a asObservable which should be returned from the repository. [Nl] - const { data } = await this.#languageRepository.requestCollection({}); - this.#languages.setValue(data?.items ?? []); - } - - async load(unique: string) { - this.resetState(); - this.#getDataPromise = this.repository.requestByUnique(unique); - - type GetDataType = Awaited>; - const { data, asObservable } = (await this.#getDataPromise) as GetDataType; + override async load(unique: string) { + const response = await super.load(unique); - if (data) { - this.#entityContext.setEntityType(UMB_DOCUMENT_ENTITY_TYPE); - this.#entityContext.setUnique(unique); - this.#isTrashedContext.setIsTrashed(data.isTrashed); - this.setIsNew(false); - this.#data.setPersisted(data); - this.#data.setCurrent(data); + if (response.data) { + this.#isTrashedContext.setIsTrashed(response.data.isTrashed); } - this.observe(asObservable(), (entity) => this.#onStoreChange(entity), 'umbDocumentStoreObserver'); - } - - #onStoreChange(entity: EntityModel | undefined) { - if (!entity) { - //TODO: This solution is alright for now. But reconsider when we introduce signal-r - history.pushState(null, '', UMB_DOCUMENTS_SECTION_PATH); - } + return response; } async create(parent: UmbEntityModel, documentTypeUnique: string, blueprintUnique?: string) { - this.resetState(); - this.#parent.setValue(parent); - if (blueprintUnique) { const blueprintRepository = new UmbDocumentBlueprintDetailRepository(this); const { data } = await blueprintRepository.requestByUnique(blueprintUnique); - this.#getDataPromise = this.repository.createScaffold({ - documentType: data?.documentType, - values: data?.values, - variants: data?.variants as Array, + return this.createScaffold({ + parent, + preset: { + documentType: data?.documentType, + values: data?.values, + variants: data?.variants as Array, + }, }); - } else { - this.#getDataPromise = this.repository.createScaffold({ + } + + return this.createScaffold({ + parent, + preset: { documentType: { unique: documentTypeUnique, collection: null, }, - }); - } - - const { data } = await this.#getDataPromise; - if (!data) return undefined; - - this.#entityContext.setEntityType(UMB_DOCUMENT_ENTITY_TYPE); - this.#entityContext.setUnique(data.unique); - this.#isTrashedContext.setIsTrashed(data.isTrashed); - this.setIsNew(true); - this.#data.setPersisted(undefined); - this.#data.setCurrent(data); - return data; + }, + }); } getCollectionAlias() { return UMB_DOCUMENT_COLLECTION_ALIAS; } - getData() { - return this.#data.getCurrent(); - } - - getUnique() { - return this.getData()?.unique; - } - - getEntityType() { - return UMB_DOCUMENT_ENTITY_TYPE; - } - - getContentTypeId() { - return this.getData()?.documentType.unique; - } - - getVaries() { - return this.#varies; - } - getVariesByCulture() { - return this.#variesByCulture; - } - getVariesBySegment() { - return this.#variesBySegment; - } - - variantById(variantId: UmbVariantId) { - return this.#data.createObservablePartOfCurrent((data) => data?.variants?.find((x) => variantId.compare(x))); - } - - getVariant(variantId: UmbVariantId) { - return this.#data.getCurrent()?.variants?.find((x) => variantId.compare(x)); - } - - getName(variantId?: UmbVariantId) { - const variants = this.#data.getCurrent()?.variants; - if (!variants) return; - if (variantId) { - return variants.find((x) => variantId.compare(x))?.name; - } else { - return variants[0]?.name; - } - } - - setName(name: string, variantId?: UmbVariantId) { - this.#data.updateVariantData(variantId ?? UmbVariantId.CreateInvariant(), { name }); - } - - name(variantId?: UmbVariantId) { - return this.#data.createObservablePartOfCurrent( - (data) => data?.variants?.find((x) => variantId?.compare(x))?.name ?? '', - ); - } - - setTemplate(templateUnique: string) { - this.#data.updateCurrent({ template: { unique: templateUnique } }); - } - - async propertyStructureById(propertyId: string) { - return this.structure.propertyStructureById(propertyId); - } - /** - * @function propertyValueByAlias - * @param {string} propertyAlias - The alias of the property - * @param {UmbVariantId} variantId - The variant - * @returns {Promise | undefined>} - An observable for the value of the property - * @description Get an Observable for the value of this property. + * Gets the unique identifier of the content type. + * @deprecated Use `getContentTypeUnique` instead. + * @returns { string | undefined} The unique identifier of the content type. + * @memberof UmbDocumentWorkspaceContext */ - async propertyValueByAlias( - propertyAlias: string, - variantId?: UmbVariantId, - ): Promise | undefined> { - return this.#data.createObservablePartOfCurrent( - (data) => - data?.values?.find((x) => x?.alias === propertyAlias && (variantId ? variantId.compare(x) : true)) - ?.value as PropertyValueType, - ); + getContentTypeId(): string | undefined { + return this.getContentTypeUnique(); } /** - * Get the current value of the property with the given alias and variantId. - * @param {string} alias - The alias of the property - * @param {UmbVariantId | undefined} variantId - The variant id of the property - * @returns {ReturnType | undefined} The value or undefined if not set or found. + * Gets the unique identifier of the content type. + * @returns { string | undefined} The unique identifier of the content type. + * @memberof UmbDocumentWorkspaceContext */ - getPropertyValue(alias: string, variantId?: UmbVariantId) { - const currentData = this.#data.getCurrent(); - if (currentData) { - const newDataSet = currentData.values?.find( - (x) => x.alias === alias && (variantId ? variantId.compare(x) : true), - ); - return newDataSet?.value as ReturnType; - } - return undefined; - } - async setPropertyValue(alias: string, value: ValueType, variantId?: UmbVariantId) { - this.initiatePropertyValueChange(); - variantId ??= UmbVariantId.CreateInvariant(); - const property = await this.structure.getPropertyStructureByAlias(alias); - - if (!property) { - throw new Error(`Property alias "${alias}" not found.`); - } - - const editorAlias = this.#dataTypeSchemaAliasMap.get(property.dataType.unique); - if (!editorAlias) { - throw new Error(`Editor Alias of "${property.dataType.unique}" not found.`); - } - - const entry = { ...variantId.toObject(), alias, editorAlias, value } as UmbDocumentValueModel; - - const currentData = this.getData(); - if (currentData) { - const values = appendToFrozenArray( - currentData.values ?? [], - entry, - (x) => x.alias === alias && variantId!.compare(x), - ); - this.#data.updateCurrent({ values }); - - // TODO: We should move this type of logic to the act of saving [NL] - this.#data.ensureVariantData(variantId); - } - this.finishPropertyValueChange(); - } - - initiatePropertyValueChange() { - this.#data.initiatePropertyValueChange(); - } - finishPropertyValueChange = () => { - this.#data.finishPropertyValueChange(); - }; - - async #determineVariantOptions() { - const options = await firstValueFrom(this.variantOptions); - - const activeVariants = this.splitView.getActiveVariants(); - const activeVariantIds = activeVariants.map((activeVariant) => UmbVariantId.Create(activeVariant)); - const changedVariantIds = this.#data.getChangedVariants(); - const selectedVariantIds = activeVariantIds.concat(changedVariantIds); - - // Selected can contain entries that are not part of the options, therefor the modal filters selection based on options. - const readOnlyCultures = this.readOnlyState.getStates().map((s) => s.variantId.culture); - let selected = selectedVariantIds.map((x) => x.toString()).filter((v, i, a) => a.indexOf(v) === i); - selected = selected.filter((x) => readOnlyCultures.includes(x) === false); - - return { - options, - selected, - }; + getContentTypeUnique(): string | undefined { + return this.getData()?.documentType.unique; } - async #performSaveOrCreate(variantIds: Array, saveData: UmbDocumentDetailModel): Promise { - if (this.getIsNew()) { - // Create: - const parent = this.#parent.getValue(); - if (!parent) throw new Error('Parent is not set'); - - const { data, error } = await this.repository.create(saveData, parent.unique); - if (!data || error) { - throw new Error('Error creating document'); - } - - this.setIsNew(false); - this.#data.setPersisted(data); - // TODO: Only update the variants that was chosen to be saved: - const currentData = this.#data.getCurrent(); - - const variantIdsIncludingInvariant = [...variantIds, UmbVariantId.CreateInvariant()]; - - const newCurrentData = await new UmbMergeContentVariantDataController(this).process( - currentData, - data, - variantIds, - variantIdsIncludingInvariant, - ); - this.#data.setCurrent(newCurrentData); - - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadChildrenOfEntityEvent({ - entityType: parent.entityType, - unique: parent.unique, - }); - eventContext.dispatchEvent(event); - } else { - // Save: - const { data, error } = await this.repository.save(saveData); - if (!data || error) { - throw new Error('Error saving document'); - } - - this.#data.setPersisted(data); - // TODO: Only update the variants that was chosen to be saved: - const currentData = this.#data.getCurrent(); - - const variantIdsIncludingInvariant = [...variantIds, UmbVariantId.CreateInvariant()]; - - const newCurrentData = await new UmbMergeContentVariantDataController(this).process( - currentData, - data, - variantIds, - variantIdsIncludingInvariant, - ); - this.#data.setCurrent(newCurrentData); - - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadStructureForEntityEvent({ - entityType: this.getEntityType(), - unique: this.getUnique()!, - }); - - eventContext.dispatchEvent(event); - } + /** + * Set the template + * @param {string} templateUnique The unique identifier of the template. + * @memberof UmbDocumentWorkspaceContext + */ + setTemplate(templateUnique: string) { + this._data.updateCurrent({ template: { unique: templateUnique } }); } - #readOnlyLanguageVariantsFilter = (option: UmbDocumentVariantOptionModel) => { - const readOnlyCultures = this.readOnlyState.getStates().map((s) => s.variantId.culture); - return readOnlyCultures.includes(option.culture) === false; - }; - async #handleSaveAndPreview() { const unique = this.getUnique(); if (!unique) throw new Error('Unique is missing'); @@ -597,13 +215,13 @@ export class UmbDocumentWorkspaceContext let culture = UMB_INVARIANT_CULTURE; // Save document (the active variant) before previewing. - const { selected } = await this.#determineVariantOptions(); + const { selected } = await this._determineVariantOptions(); if (selected.length > 0) { culture = selected[0]; const variantIds = [UmbVariantId.FromString(culture)]; - const saveData = await this.#data.constructData(variantIds); - await this.#runMandatoryValidationForSaveData(saveData); - await this.#performSaveOrCreate(variantIds, saveData); + const saveData = await this._data.constructData(variantIds); + await this._runMandatoryValidationForSaveData(saveData); + await this._performCreateOrUpdate(variantIds, saveData); } // Tell the server that we're entering preview mode. @@ -628,7 +246,7 @@ export class UmbDocumentWorkspaceContext let variantIds: Array = []; - const { options, selected } = await this.#determineVariantOptions(); + const { options, selected } = await this._determineVariantOptions(); // If there is only one variant, we don't need to open the modal. if (options.length === 0) { @@ -643,7 +261,7 @@ export class UmbDocumentWorkspaceContext .open(this, UMB_DOCUMENT_PUBLISH_MODAL, { data: { options, - pickableFilter: this.#readOnlyLanguageVariantsFilter, + pickableFilter: this._readOnlyLanguageVariantsFilter, }, value: { selection: selected }, }) @@ -655,15 +273,15 @@ export class UmbDocumentWorkspaceContext variantIds = result?.selection.map((x) => UmbVariantId.FromString(x)) ?? []; } - const saveData = await this.#data.constructData(variantIds); - await this.#runMandatoryValidationForSaveData(saveData); + const saveData = await this._data.constructData(variantIds); + await this._runMandatoryValidationForSaveData(saveData); // Create the validation repository if it does not exist. (we first create this here when we need it) [NL] this.#validationRepository ??= new UmbDocumentValidationRepository(this); // We ask the server first to get a concatenated set of validation messages. So we see both front-end and back-end validation messages [NL] if (this.getIsNew()) { - const parent = this.#parent.getValue(); + const parent = this.getParent(); if (!parent) throw new Error('Parent is not set'); this.#serverValidation.askServerForValidation( saveData, @@ -683,7 +301,7 @@ export class UmbDocumentWorkspaceContext }, async () => { // If data of the selection is not valid Then just save: - await this.#performSaveOrCreate(variantIds, saveData); + await this._performCreateOrUpdate(variantIds, saveData); // Notifying that the save was successful, but we did not publish, which is what we want to symbolize here. [NL] const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT); // TODO: Get rid of the save notification. @@ -697,28 +315,11 @@ export class UmbDocumentWorkspaceContext ); } - async #runMandatoryValidationForSaveData(saveData: UmbDocumentDetailModel) { - // Check that the data is valid before we save it. - // Check variants have a name: - const variantsWithoutAName = saveData.variants.filter((x) => !x.name); - if (variantsWithoutAName.length > 0) { - const validationContext = await this.getContext(UMB_VALIDATION_CONTEXT); - variantsWithoutAName.forEach((variant) => { - validationContext.messages.addMessage( - 'client', - `$.variants[${UmbDataPathVariantQuery(variant)}].name`, - UMB_VALIDATION_EMPTY_LOCALIZATION_KEY, - ); - }); - throw new Error('All variants must have a name'); - } - } - async #performSaveAndPublish(variantIds: Array, saveData: UmbDocumentDetailModel): Promise { const unique = this.getUnique(); if (!unique) throw new Error('Unique is missing'); - await this.#performSaveOrCreate(variantIds, saveData); + await this._performCreateOrUpdate(variantIds, saveData); const { error } = await this.publishingRepository.publish( unique, @@ -736,52 +337,6 @@ export class UmbDocumentWorkspaceContext } } - async #handleSave() { - const { options, selected } = await this.#determineVariantOptions(); - - let variantIds: Array = []; - - // If there is only one variant, we don't need to open the modal. - if (options.length === 0) { - throw new Error('No variants are available'); - } else if (options.length === 1) { - // If only one option we will skip ahead and save the document with the only variant available: - variantIds.push(UmbVariantId.Create(options[0])); - } else { - // If there are multiple variants, we will open the modal to let the user pick which variants to save. - const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); - const result = await modalManagerContext - .open(this, UMB_DOCUMENT_SAVE_MODAL, { - data: { - options, - pickableFilter: this.#readOnlyLanguageVariantsFilter, - }, - value: { selection: selected }, - }) - .onSubmit() - .catch(() => undefined); - - if (!result?.selection.length) return; - - variantIds = result?.selection.map((x) => UmbVariantId.FromString(x)) ?? []; - } - - const saveData = await this.#data.constructData(variantIds); - await this.#runMandatoryValidationForSaveData(saveData); - return await this.#performSaveOrCreate(variantIds, saveData); - } - - public override requestSubmit() { - return this.#handleSave(); - } - - public submit() { - return this.#handleSave(); - } - public override invalidSubmit() { - return this.#handleSave(); - } - public async publish() { throw new Error('Method not implemented.'); } @@ -795,14 +350,14 @@ export class UmbDocumentWorkspaceContext } public async schedule() { - const { options, selected } = await this.#determineVariantOptions(); + const { options, selected } = await this._determineVariantOptions(); const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); const result = await modalManagerContext .open(this, UMB_DOCUMENT_SCHEDULE_MODAL, { data: { options, - pickableFilter: this.#readOnlyLanguageVariantsFilter, + pickableFilter: this._readOnlyLanguageVariantsFilter, }, value: { selection: selected.map((unique) => ({ unique, schedule: {} })) }, }) @@ -844,14 +399,14 @@ export class UmbDocumentWorkspaceContext const entityType = this.getEntityType(); if (!entityType) throw new Error('Entity type is missing'); - const { options, selected } = await this.#determineVariantOptions(); + const { options, selected } = await this._determineVariantOptions(); const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); const result = await modalManagerContext .open(this, UMB_DOCUMENT_PUBLISH_WITH_DESCENDANTS_MODAL, { data: { options, - pickableFilter: this.#readOnlyLanguageVariantsFilter, + pickableFilter: this._readOnlyLanguageVariantsFilter, }, value: { selection: selected }, }) @@ -891,25 +446,12 @@ export class UmbDocumentWorkspaceContext } } - async delete() { - const id = this.getUnique(); - if (id) { - await this.repository.delete(id); - } - } - public createPropertyDatasetContext( host: UmbControllerHost, variantId: UmbVariantId, ): UmbDocumentPropertyDatasetContext { return new UmbDocumentPropertyDatasetContext(host, this, variantId); } - - public override destroy(): void { - this.structure.destroy(); - this.#languageRepository.destroy(); - super.destroy(); - } } export default UmbDocumentWorkspaceContext; diff --git a/src/packages/extension-insights/workspace/manifests.ts b/src/packages/extension-insights/workspace/manifests.ts index 6cd95677fb..6ea2f304e3 100644 --- a/src/packages/extension-insights/workspace/manifests.ts +++ b/src/packages/extension-insights/workspace/manifests.ts @@ -1,7 +1,7 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_EXTENSION_COLLECTION_ALIAS } from '../collection/manifests.js'; import { UMB_EXTENSION_ROOT_ENTITY_TYPE } from '../entity.js'; import { UMB_EXTENSION_ROOT_WORKSPACE_ALIAS } from './constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/language/workspace/language-root/manifests.ts b/src/packages/language/workspace/language-root/manifests.ts index e0fef2efe7..92e59e4341 100644 --- a/src/packages/language/workspace/language-root/manifests.ts +++ b/src/packages/language/workspace/language-root/manifests.ts @@ -1,7 +1,7 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_LANGUAGE_COLLECTION_ALIAS } from '../../collection/index.js'; import { UMB_LANGUAGE_ROOT_ENTITY_TYPE } from '../../entity.js'; import { UMB_LANGUAGE_ROOT_WORKSPACE_ALIAS } from './constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { @@ -27,7 +27,7 @@ export const manifests: Array = [ }, conditions: [ { - alias:UMB_WORKSPACE_CONDITION_ALIAS, + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_LANGUAGE_ROOT_WORKSPACE_ALIAS, }, ], diff --git a/src/packages/language/workspace/language/manifests.ts b/src/packages/language/workspace/language/manifests.ts index b0e3508017..e3e45525fa 100644 --- a/src/packages/language/workspace/language/manifests.ts +++ b/src/packages/language/workspace/language/manifests.ts @@ -1,6 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_LANGUAGE_WORKSPACE_ALIAS } from './constants.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/media/media-types/menu/manifests.ts b/src/packages/media/media-types/menu/manifests.ts index 7295ae54db..b3a58cb417 100644 --- a/src/packages/media/media-types/menu/manifests.ts +++ b/src/packages/media/media-types/menu/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_MEDIA_TYPE_TREE_ALIAS } from '../tree/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/media/media-types/tree/folder/workspace/manifests.ts b/src/packages/media/media-types/tree/folder/workspace/manifests.ts index 00f6310476..6fa8991742 100644 --- a/src/packages/media/media-types/tree/folder/workspace/manifests.ts +++ b/src/packages/media/media-types/tree/folder/workspace/manifests.ts @@ -1,7 +1,6 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_MEDIA_TYPE_FOLDER_ENTITY_TYPE } from '../../../entity.js'; import { UMB_MEDIA_TYPE_FOLDER_WORKSPACE_ALIAS } from './constants.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/media/media-types/workspace/manifests.ts b/src/packages/media/media-types/workspace/manifests.ts index 5fd768d67b..6076139d14 100644 --- a/src/packages/media/media-types/workspace/manifests.ts +++ b/src/packages/media/media-types/workspace/manifests.ts @@ -1,8 +1,6 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_MEDIA_TYPE_COMPOSITION_REPOSITORY_ALIAS } from '../repository/index.js'; import { UMB_MEDIA_TYPE_WORKSPACE_ALIAS } from './constants.js'; - -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/media/media/collection/action/create-media-collection-action.element.ts b/src/packages/media/media/collection/action/create-media-collection-action.element.ts index 0b3faf83a1..1a14b5ec76 100644 --- a/src/packages/media/media/collection/action/create-media-collection-action.element.ts +++ b/src/packages/media/media/collection/action/create-media-collection-action.element.ts @@ -9,6 +9,7 @@ import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; import type { ManifestCollectionAction } from '@umbraco-cms/backoffice/collection'; import type { UmbAllowedMediaTypeModel } from '@umbraco-cms/backoffice/media-type'; +import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; @customElement('umb-create-media-collection-action') export class UmbCreateMediaCollectionActionElement extends UmbLitElement { @@ -22,7 +23,7 @@ export class UmbCreateMediaCollectionActionElement extends UmbLitElement { private _currentView?: string; @state() - private _mediaUnique?: string; + private _mediaUnique?: UmbEntityUnique; @state() private _mediaTypeUnique?: string; @@ -54,6 +55,7 @@ export class UmbCreateMediaCollectionActionElement extends UmbLitElement { this.observe(workspaceContext.unique, (unique) => { this._mediaUnique = unique; }); + this.observe(workspaceContext.contentTypeUnique, (mediaTypeUnique) => { this._mediaTypeUnique = mediaTypeUnique; }); diff --git a/src/packages/media/media/components/input-image-cropper/crop-change.event.ts b/src/packages/media/media/components/input-image-cropper/crop-change.event.ts index 0c9b076fba..b812509444 100644 --- a/src/packages/media/media/components/input-image-cropper/crop-change.event.ts +++ b/src/packages/media/media/components/input-image-cropper/crop-change.event.ts @@ -5,4 +5,4 @@ export class UmbImageCropChangeEvent extends Event { // mimics the native change event super(UmbImageCropChangeEvent.TYPE, { bubbles: false, composed: false, cancelable: false, ...args }); } -} \ No newline at end of file +} diff --git a/src/packages/media/media/components/input-image-cropper/focalpoint-change.event.ts b/src/packages/media/media/components/input-image-cropper/focalpoint-change.event.ts index ba38c5e87f..596a03b559 100644 --- a/src/packages/media/media/components/input-image-cropper/focalpoint-change.event.ts +++ b/src/packages/media/media/components/input-image-cropper/focalpoint-change.event.ts @@ -10,4 +10,4 @@ export class UmbFocalPointChangeEvent extends Event { super(UmbFocalPointChangeEvent.TYPE, { bubbles: false, composed: false, cancelable: false, ...args }); this.focalPoint = focalPoint; } -} \ No newline at end of file +} diff --git a/src/packages/media/media/components/input-image-cropper/image-cropper-field.element.ts b/src/packages/media/media/components/input-image-cropper/image-cropper-field.element.ts index 3ca9812849..5ba36c22b8 100644 --- a/src/packages/media/media/components/input-image-cropper/image-cropper-field.element.ts +++ b/src/packages/media/media/components/input-image-cropper/image-cropper-field.element.ts @@ -1,19 +1,20 @@ -import './image-cropper.element.js'; -import './image-cropper-focus-setter.element.js'; -import './image-cropper-preview.element.js'; import type { UmbImageCropperElement } from './image-cropper.element.js'; import type { UmbImageCropperCrop, UmbImageCropperCrops, UmbImageCropperFocalPoint, UmbImageCropperPropertyEditorValue, - UmbFocalPointChangeEvent, - UmbImageCropChangeEvent, -} from './index.js'; +} from './types.js'; +import type { UmbImageCropChangeEvent } from './crop-change.event.js'; +import type { UmbFocalPointChangeEvent } from './focalpoint-change.event.js'; import { css, customElement, html, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import './image-cropper.element.js'; +import './image-cropper-focus-setter.element.js'; +import './image-cropper-preview.element.js'; + @customElement('umb-image-cropper-field') export class UmbInputImageCropperFieldElement extends UmbLitElement { @property({ attribute: false }) diff --git a/src/packages/media/media/components/input-image-cropper/image-cropper-focus-setter.element.ts b/src/packages/media/media/components/input-image-cropper/image-cropper-focus-setter.element.ts index 0963940875..5951fe8079 100644 --- a/src/packages/media/media/components/input-image-cropper/image-cropper-focus-setter.element.ts +++ b/src/packages/media/media/components/input-image-cropper/image-cropper-focus-setter.element.ts @@ -1,8 +1,19 @@ -import { type UmbImageCropperFocalPoint, UmbFocalPointChangeEvent } from './index.js'; import type { UmbFocalPointModel } from '../../types.js'; +import type { UmbImageCropperFocalPoint } from './types.js'; +import { UmbFocalPointChangeEvent } from './focalpoint-change.event.js'; import { drag } from '@umbraco-cms/backoffice/external/uui'; import { clamp } from '@umbraco-cms/backoffice/utils'; -import { css, customElement, classMap, ifDefined, html, nothing, state, property, query } from '@umbraco-cms/backoffice/external/lit'; +import { + css, + customElement, + classMap, + ifDefined, + html, + nothing, + state, + property, + query, +} from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; @@ -38,7 +49,7 @@ export class UmbImageCropperFocusSetterElement extends UmbLitElement { @property({ type: Boolean }) hideFocalPoint = false; - + @property({ type: Boolean, reflect: true }) disabled = false; @@ -84,7 +95,7 @@ export class UmbImageCropperFocusSetterElement extends UmbLitElement { } this.#resetCoords(); - + this.imageElement.style.aspectRatio = `${imageAspectRatio}`; this.wrapperElement.style.aspectRatio = `${imageAspectRatio}`; }; @@ -97,22 +108,21 @@ export class UmbImageCropperFocusSetterElement extends UmbLitElement { } #coordsToFactor(x: number, y: number) { - const top = (y / 100) / y * 50; - const left = (x / 100) / x * 50; + const top = (y / 100 / y) * 50; + const left = (x / 100 / x) * 50; - return { top, left }; + return { top, left }; } #setFocalPoint(x: number, y: number, width: number, height: number) { - - const left = clamp((x / width), 0, 1); - const top = clamp((y / height), 0, 1); + const left = clamp(x / width, 0, 1); + const top = clamp(y / height, 0, 1); this.#coordsToFactor(x, y); const focalPoint = { left, top } as UmbFocalPointModel; - console.log("setFocalPoint", focalPoint) + console.log('setFocalPoint', focalPoint); this.dispatchEvent(new UmbFocalPointChangeEvent(focalPoint)); } @@ -171,7 +181,7 @@ export class UmbImageCropperFocusSetterElement extends UmbLitElement { #handleGridKeyDown(event: KeyboardEvent) { if (this.disabled || this.hideFocalPoint) return; - + const increment = event.shiftKey ? 1 : 10; const grid = this.wrapperElement; @@ -207,14 +217,13 @@ export class UmbImageCropperFocusSetterElement extends UmbLitElement { override render() { if (!this.src) return nothing; return html` -
    +
    nothing} src=${this.src} alt="" /> -
    - - + +
    `; } diff --git a/src/packages/media/media/components/input-rich-media/input-rich-media.element.ts b/src/packages/media/media/components/input-rich-media/input-rich-media.element.ts index 24a92a2af8..c71255813e 100644 --- a/src/packages/media/media/components/input-rich-media/input-rich-media.element.ts +++ b/src/packages/media/media/components/input-rich-media/input-rich-media.element.ts @@ -1,10 +1,9 @@ -import { UmbInputMediaElement } from '../input-media/index.js'; import { UmbMediaItemRepository } from '../../repository/index.js'; import { UMB_IMAGE_CROPPER_EDITOR_MODAL, UMB_MEDIA_PICKER_MODAL } from '../../modals/index.js'; import type { UmbCropModel, UmbMediaPickerPropertyValue } from '../../types.js'; import type { UmbMediaItemModel } from '../../repository/index.js'; import type { UmbUploadableItem } from '../../dropzone/types.js'; -import { customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit'; +import { css, customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit'; import { umbConfirmModal, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbId } from '@umbraco-cms/backoffice/id'; @@ -414,7 +413,42 @@ export class UmbInputRichMediaElement extends UUIFormControlMixin(UmbLitElement, `; } - static override styles = UmbInputMediaElement.styles; + static override styles = [ + css` + :host { + position: relative; + } + .container { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + grid-auto-rows: 150px; + gap: var(--uui-size-space-5); + } + + #btn-add { + text-align: center; + height: 100%; + } + + uui-icon { + display: block; + margin: 0 auto; + } + + uui-card-media umb-icon { + font-size: var(--uui-size-8); + } + + uui-card-media[drag-placeholder] { + opacity: 0.2; + } + img { + background-image: url('data:image/svg+xml;charset=utf-8,'); + background-size: 10px 10px; + background-repeat: repeat; + } + `, + ]; } export default UmbInputRichMediaElement; diff --git a/src/packages/media/media/menu/manifests.ts b/src/packages/media/media/menu/manifests.ts index 1681aedd98..f9e666fec0 100644 --- a/src/packages/media/media/menu/manifests.ts +++ b/src/packages/media/media/menu/manifests.ts @@ -1,6 +1,6 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_MEDIA_TREE_ALIAS } from '../tree/index.js'; import { UMB_MEDIA_MENU_ALIAS } from './constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/media/media/property-editors/media-entity-picker/property-editor-ui-media-entity-picker.element.ts b/src/packages/media/media/property-editors/media-entity-picker/property-editor-ui-media-entity-picker.element.ts index 46e8af9c85..224b4cbdbc 100644 --- a/src/packages/media/media/property-editors/media-entity-picker/property-editor-ui-media-entity-picker.element.ts +++ b/src/packages/media/media/property-editors/media-entity-picker/property-editor-ui-media-entity-picker.element.ts @@ -1,8 +1,8 @@ +import type { UmbInputMediaElement } from '../../components/index.js'; import { html, customElement, 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 type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models'; -import type { UmbInputMediaElement } from '@umbraco-cms/backoffice/media'; import type { UmbPropertyEditorConfigCollection, UmbPropertyEditorUiElement, diff --git a/src/packages/media/media/tree/media-tree-picker-modal.token.ts b/src/packages/media/media/tree/media-tree-picker-modal.token.ts index 2ba9bba5cd..30cae5a481 100644 --- a/src/packages/media/media/tree/media-tree-picker-modal.token.ts +++ b/src/packages/media/media/tree/media-tree-picker-modal.token.ts @@ -1,10 +1,10 @@ +import type { UmbMediaTreeItemModel } from './types.js'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; import { type UmbTreePickerModalValue, type UmbTreePickerModalData, UMB_TREE_PICKER_MODAL_ALIAS, } from '@umbraco-cms/backoffice/tree'; -import type { UmbMediaTreeItemModel } from '@umbraco-cms/backoffice/media'; export type UmbMediaTreePickerModalData = UmbTreePickerModalData; export type UmbMediaTreePickerModalValue = UmbTreePickerModalValue; diff --git a/src/packages/media/media/workspace/manifests.ts b/src/packages/media/media/workspace/manifests.ts index a48480ee07..21daa964dd 100644 --- a/src/packages/media/media/workspace/manifests.ts +++ b/src/packages/media/media/workspace/manifests.ts @@ -1,7 +1,6 @@ -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UmbSubmitWorkspaceAction, UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin'; import { UMB_CONTENT_HAS_PROPERTIES_WORKSPACE_CONDITION } from '@umbraco-cms/backoffice/content'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_MEDIA_WORKSPACE_ALIAS = 'Umb.Workspace.Media'; diff --git a/src/packages/media/media/workspace/media-workspace.context.ts b/src/packages/media/media/workspace/media-workspace.context.ts index a5a2a5133d..fa3dcd1a52 100644 --- a/src/packages/media/media/workspace/media-workspace.context.ts +++ b/src/packages/media/media/workspace/media-workspace.context.ts @@ -1,192 +1,58 @@ import { UmbMediaTypeDetailRepository } from '../../media-types/repository/detail/media-type-detail.repository.js'; import { UmbMediaPropertyDatasetContext } from '../property-dataset-context/media-property-dataset-context.js'; import { UMB_MEDIA_ENTITY_TYPE } from '../entity.js'; -import { UmbMediaDetailRepository } from '../repository/index.js'; -import type { - UmbMediaDetailModel, - UmbMediaValueModel, - UmbMediaVariantModel, - UmbMediaVariantOptionModel, -} from '../types.js'; +import type { UmbMediaDetailRepository } from '../repository/index.js'; +import { UMB_MEDIA_DETAIL_REPOSITORY_ALIAS } from '../repository/index.js'; +import type { UmbMediaDetailModel, UmbMediaVariantModel } from '../types.js'; import { UMB_CREATE_MEDIA_WORKSPACE_PATH_PATTERN, UMB_EDIT_MEDIA_WORKSPACE_PATH_PATTERN } from '../paths.js'; -import { UMB_MEDIA_SECTION_PATH } from '../../media-section/paths.js'; import { UMB_MEDIA_COLLECTION_ALIAS } from '../collection/index.js'; import { UMB_MEMBER_DETAIL_MODEL_VARIANT_SCAFFOLD } from './constants.js'; -import { UMB_INVARIANT_CULTURE, UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import { UmbContentTypeStructureManager } from '@umbraco-cms/backoffice/content-type'; +import { UMB_MEDIA_WORKSPACE_ALIAS } from './manifests.js'; +import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import { - UmbSubmittableWorkspaceContextBase, UmbWorkspaceIsNewRedirectController, UmbWorkspaceIsNewRedirectControllerAlias, - UmbWorkspaceSplitViewManager, } from '@umbraco-cms/backoffice/workspace'; -import { - appendToFrozenArray, - mergeObservables, - UmbArrayState, - UmbObjectState, -} from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; -import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; -import { - UmbRequestReloadChildrenOfEntityEvent, - UmbRequestReloadStructureForEntityEvent, -} from '@umbraco-cms/backoffice/entity-action'; import type { UmbMediaTypeDetailModel } from '@umbraco-cms/backoffice/media-type'; import { - UmbContentWorkspaceDataManager, + UmbContentDetailWorkspaceContextBase, type UmbContentCollectionWorkspaceContext, type UmbContentWorkspaceContext, } from '@umbraco-cms/backoffice/content'; -import { UmbEntityContext } from '@umbraco-cms/backoffice/entity'; import { UmbIsTrashedEntityContext } from '@umbraco-cms/backoffice/recycle-bin'; -import { UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; -import { UmbDataTypeItemRepositoryManager } from '@umbraco-cms/backoffice/data-type'; type ContentModel = UmbMediaDetailModel; type ContentTypeModel = UmbMediaTypeDetailModel; + export class UmbMediaWorkspaceContext - extends UmbSubmittableWorkspaceContextBase + extends UmbContentDetailWorkspaceContextBase< + ContentModel, + UmbMediaDetailRepository, + ContentTypeModel, + UmbMediaVariantModel + > implements UmbContentWorkspaceContext, UmbContentCollectionWorkspaceContext { - public readonly IS_CONTENT_WORKSPACE_CONTEXT = true as const; - - public readonly repository = new UmbMediaDetailRepository(this); - - #parent = new UmbObjectState<{ entityType: string; unique: string | null } | undefined>(undefined); - readonly parentUnique = this.#parent.asObservablePart((parent) => (parent ? parent.unique : undefined)); - readonly parentEntityType = this.#parent.asObservablePart((parent) => (parent ? parent.entityType : undefined)); - - readonly #data = new UmbContentWorkspaceDataManager(this, UMB_MEMBER_DETAIL_MODEL_VARIANT_SCAFFOLD); - - #getDataPromise?: Promise; - // TODo: Optimize this so it uses either a App Language Context? [NL] - #languageRepository = new UmbLanguageCollectionRepository(this); - #languages = new UmbArrayState([], (x) => x.unique); - public readonly languages = this.#languages.asObservable(); - - readOnlyState = new UmbReadOnlyVariantStateManager(this); - - public isLoaded() { - return this.#getDataPromise; - } - - readonly unique = this.#data.createObservablePartOfCurrent((data) => data?.unique); - readonly entityType = this.#data.createObservablePartOfCurrent((data) => data?.entityType); - readonly contentTypeUnique = this.#data.createObservablePartOfCurrent((data) => data?.mediaType.unique); - readonly contentTypeHasCollection = this.#data.createObservablePartOfCurrent((data) => !!data?.mediaType.collection); + readonly contentTypeUnique = this._data.createObservablePartOfCurrent((data) => data?.mediaType.unique); + readonly contentTypeHasCollection = this._data.createObservablePartOfCurrent((data) => !!data?.mediaType.collection); - readonly variants = this.#data.createObservablePartOfCurrent((data) => data?.variants || []); + readonly urls = this._data.createObservablePartOfCurrent((data) => data?.urls || []); - readonly values = this.#data.createObservablePartOfCurrent((data) => data?.values); - getValues() { - return this.#data.getCurrent()?.values; - } - - readonly urls = this.#data.createObservablePartOfCurrent((data) => data?.urls || []); - - readonly structure = new UmbContentTypeStructureManager(this, new UmbMediaTypeDetailRepository(this)); - readonly variesByCulture = this.structure.ownerContentTypeObservablePart((x) => x?.variesByCulture); - readonly variesBySegment = this.structure.ownerContentTypeObservablePart((x) => x?.variesBySegment); - readonly varies = this.structure.ownerContentTypeObservablePart((x) => - x ? x.variesByCulture || x.variesBySegment : undefined, - ); - #varies?: boolean; - #variesByCulture?: boolean; - #variesBySegment?: boolean; - - readonly #dataTypeItemManager = new UmbDataTypeItemRepositoryManager(this); - #dataTypeSchemaAliasMap = new Map(); - - readonly splitView = new UmbWorkspaceSplitViewManager(); - - readonly variantOptions = mergeObservables( - [this.varies, this.variants, this.languages], - ([varies, variants, languages]) => { - // TODO: When including segments, when be aware about the case of segment varying when not culture varying. [NL] - if (varies === true) { - return languages.map((language) => { - return { - variant: variants.find((x) => x.culture === language.unique), - language, - // TODO: When including segments, this object should be updated to include a object for the segment. [NL] - // TODO: When including segments, the unique should be updated to include the segment as well. [NL] - unique: language.unique, // This must be a variantId string! - culture: language.unique, - segment: null, - } as UmbMediaVariantOptionModel; - }); - } else if (varies === false) { - return [ - { - variant: variants.find((x) => x.culture === null), - language: languages.find((x) => x.isDefault), - culture: null, - segment: null, - unique: UMB_INVARIANT_CULTURE, // This must be a variantId string! - } as UmbMediaVariantOptionModel, - ]; - } - return [] as Array; - }, - ); - - // TODO: this should be set up for all entity workspace contexts in a base class - #entityContext = new UmbEntityContext(this); - // TODO: this might not be the correct place to spin this up #isTrashedContext = new UmbIsTrashedEntityContext(this); constructor(host: UmbControllerHost) { - super(host, 'Umb.Workspace.Media'); + super(host, { + entityType: UMB_MEDIA_ENTITY_TYPE, + workspaceAlias: UMB_MEDIA_WORKSPACE_ALIAS, + detailRepositoryAlias: UMB_MEDIA_DETAIL_REPOSITORY_ALIAS, + contentTypeDetailRepository: UmbMediaTypeDetailRepository, + contentVariantScaffold: UMB_MEMBER_DETAIL_MODEL_VARIANT_SCAFFOLD, + }); this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique), null); - this.observe( - this.varies, - (varies) => { - this.#data.setVaries(varies); - this.#varies = varies; - }, - null, - ); - this.observe( - this.variesByCulture, - (varies) => { - this.#data.setVariesByCulture(varies); - this.#variesByCulture = varies; - }, - null, - ); - this.observe( - this.variesBySegment, - (varies) => { - this.#data.setVariesBySegment(varies); - this.#variesBySegment = varies; - }, - null, - ); - this.observe( - this.structure.contentTypeDataTypeUniques, - (dataTypeUniques: Array) => { - this.#dataTypeItemManager.setUniques(dataTypeUniques); - }, - null, - ); - this.observe( - this.#dataTypeItemManager.items, - (dataTypes) => { - // Make a map of the data type unique and editorAlias: - this.#dataTypeSchemaAliasMap = new Map( - dataTypes.map((dataType) => { - return [dataType.unique, dataType.propertyEditorSchemaAlias]; - }), - ); - }, - null, - ); - this.loadLanguages(); this.routes.setRoutes([ { @@ -196,7 +62,10 @@ export class UmbMediaWorkspaceContext const parentEntityType = info.match.params.entityType; const parentUnique = info.match.params.parentUnique === 'null' ? null : info.match.params.parentUnique; const mediaTypeUnique = info.match.params.mediaTypeUnique; - await this.create({ entityType: parentEntityType, unique: parentUnique }, mediaTypeUnique); + await this.createScaffold({ + parent: { entityType: parentEntityType, unique: parentUnique }, + preset: { mediaType: { unique: mediaTypeUnique, collection: null } }, + }); new UmbWorkspaceIsNewRedirectController( this, @@ -217,245 +86,53 @@ export class UmbMediaWorkspaceContext ]); } - override resetState() { + public override resetState() { super.resetState(); - this.#data.clear(); - } - - async loadLanguages() { - // TODO: If we don't end up having a Global Context for languages, then we should at least change this into using a asObservable which should be returned from the repository. [Nl] - const { data } = await this.#languageRepository.requestCollection({}); - this.#languages.setValue(data?.items ?? []); + this.removeUmbControllerByAlias(UmbWorkspaceIsNewRedirectControllerAlias); } - async load(unique: string) { - this.resetState(); - this.#getDataPromise = this.repository.requestByUnique(unique); - type GetDataType = Awaited>; - const { data, asObservable } = (await this.#getDataPromise) as GetDataType; + public override async load(unique: string) { + const response = await super.load(unique); - if (data) { - this.#entityContext.setEntityType(UMB_MEDIA_ENTITY_TYPE); - this.#entityContext.setUnique(unique); - this.#isTrashedContext.setIsTrashed(data.isTrashed); - this.setIsNew(false); - this.#data.setPersisted(data); - this.#data.setCurrent(data); + if (response.data) { + this.#isTrashedContext.setIsTrashed(response.data.isTrashed); } - this.observe(asObservable(), (entity) => this.#onStoreChange(entity), 'umbMediaStoreObserver'); - } - - #onStoreChange(entity: ContentModel | undefined) { - if (!entity) { - //TODO: This solution is alright for now. But reconsider when we introduce signal-r - history.pushState(null, '', UMB_MEDIA_SECTION_PATH); - } + return response; } - async create(parent: { entityType: string; unique: string | null }, mediaTypeUnique: string) { - this.resetState(); - this.#parent.setValue(parent); - this.#getDataPromise = this.repository.createScaffold({ mediaType: { unique: mediaTypeUnique, collection: null } }); - const { data } = await this.#getDataPromise; - if (!data) return undefined; - - this.#entityContext.setEntityType(UMB_MEDIA_ENTITY_TYPE); - this.#entityContext.setUnique(data.unique); - this.setIsNew(true); - this.#data.setPersisted(undefined); - this.#data.setCurrent(data); - return data; + /* + * @deprecated Use `createScaffold` instead. + */ + public async create(parent: { entityType: string; unique: string | null }, mediaTypeUnique: string) { + const args = { + parent, + preset: { mediaType: { unique: mediaTypeUnique, collection: null } }, + }; + return this.createScaffold(args); } - getCollectionAlias() { + public getCollectionAlias() { return UMB_MEDIA_COLLECTION_ALIAS; } - getData() { - return this.#data.getCurrent(); - } - - getUnique() { - return this.getData()?.unique; - } - - getEntityType() { - return UMB_MEDIA_ENTITY_TYPE; - } - - getContentTypeId() { - return this.getData()?.mediaType.unique; - } - - getVaries() { - return this.#varies; - } - getVariesByCulture() { - return this.#variesByCulture; - } - getVariesBySegment() { - return this.#variesBySegment; - } - - variantById(variantId: UmbVariantId) { - return this.#data.createObservablePartOfCurrent((data) => data?.variants?.find((x) => variantId.compare(x))); - } - - getVariant(variantId: UmbVariantId) { - return this.#data.getCurrent()?.variants?.find((x) => variantId.compare(x)); - } - - getName(variantId?: UmbVariantId) { - const variants = this.#data.getCurrent()?.variants; - if (!variants) return; - if (variantId) { - return variants.find((x) => variantId.compare(x))?.name; - } else { - return variants[0]?.name; - } - } - - setName(name: string, variantId?: UmbVariantId) { - this.#data.updateVariantData(variantId ?? UmbVariantId.CreateInvariant(), { name }); - } - - name(variantId?: UmbVariantId) { - return this.#data.createObservablePartOfCurrent( - (data) => data?.variants?.find((x) => variantId?.compare(x))?.name ?? '', - ); - } - - async propertyStructureById(propertyId: string) { - return this.structure.propertyStructureById(propertyId); - } /** - * @function propertyValueByAlias - * @param {string} propertyAlias - * @param {UmbVariantId} variantId - * @returns {Promise | undefined>} - * @description Get an Observable for the value of this property. + * Gets the unique identifier of the content type. + * @deprecated Use `getContentTypeUnique` instead. + * @returns { string | undefined} The unique identifier of the content type. + * @memberof UmbMediaWorkspaceContext */ - async propertyValueByAlias(propertyAlias: string, variantId?: UmbVariantId) { - return this.#data.createObservablePartOfCurrent( - (data) => - data?.values?.find((x) => x?.alias === propertyAlias && (variantId ? variantId.compare(x) : true)) - ?.value as PropertyValueType, - ); + getContentTypeId(): string | undefined { + return this.getContentTypeUnique(); } /** - * Get the current value of the property with the given alias and variantId. - * @param {string} alias - * @param {UmbVariantId} variantId - * @returns The value or undefined if not set or found. + * Gets the unique identifier of the content type. + * @returns { string | undefined} The unique identifier of the content type. + * @memberof UmbMediaWorkspaceContext */ - getPropertyValue(alias: string, variantId?: UmbVariantId) { - const currentData = this.#data.getCurrent(); - if (currentData) { - const newDataSet = currentData.values?.find( - (x) => x.alias === alias && (variantId ? variantId.compare(x) : true), - ); - return newDataSet?.value as ReturnType; - } - return undefined; - } - - async setPropertyValue(alias: string, value: ValueType, variantId?: UmbVariantId) { - this.initiatePropertyValueChange(); - variantId ??= UmbVariantId.CreateInvariant(); - const property = await this.structure.getPropertyStructureByAlias(alias); - - if (!property) { - throw new Error(`Property alias "${alias}" not found.`); - } - - const editorAlias = this.#dataTypeSchemaAliasMap.get(property.dataType.unique); - if (!editorAlias) { - throw new Error(`Editor Alias of "${property.dataType.unique}" not found.`); - } - - const entry = { ...variantId.toObject(), alias, editorAlias, value } as UmbMediaValueModel; - - const currentData = this.getData(); - if (currentData) { - const values = appendToFrozenArray( - currentData.values ?? [], - entry, - (x) => x.alias === alias && variantId!.compare(x), - ); - this.#data.updateCurrent({ values }); - - // TODO: We should move this type of logic to the act of saving [NL] - this.#data.ensureVariantData(variantId); - } - this.finishPropertyValueChange(); - } - - initiatePropertyValueChange() { - this.#data.initiatePropertyValueChange(); - } - finishPropertyValueChange = () => { - this.#data.finishPropertyValueChange(); - }; - - async #handleSave() { - const saveData = this.#data.getCurrent(); - if (!saveData?.unique) throw new Error('Unique is missing'); - - if (this.getIsNew()) { - const parent = this.#parent.getValue(); - if (!parent) throw new Error('Parent is not set'); - - const { data, error } = await this.repository.create(saveData, parent.unique); - if (!data || error) { - throw new Error('Error creating document'); - } - - this.setIsNew(false); - this.#data.setPersisted(data); - this.#data.setCurrent(data); - - // TODO: this might not be the right place to alert the tree, but it works for now - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadChildrenOfEntityEvent({ - entityType: parent.entityType, - unique: parent.unique, - }); - eventContext.dispatchEvent(event); - } else { - // Save: - const { data, error } = await this.repository.save(saveData); - if (!data || error) { - throw new Error('Error saving document'); - } - - this.#data.setPersisted(data); - this.#data.setCurrent(data); - - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadStructureForEntityEvent({ - unique: this.getUnique()!, - entityType: this.getEntityType(), - }); - - eventContext.dispatchEvent(event); - } - } - - async submit() { - const data = this.getData(); - if (!data) { - throw new Error('Data is missing'); - } - await this.#handleSave(); - } - - async delete() { - const id = this.getUnique(); - if (id) { - await this.repository.delete(id); - } + getContentTypeUnique(): string | undefined { + return this.getData()?.mediaType.unique; } public createPropertyDatasetContext( @@ -464,12 +141,6 @@ export class UmbMediaWorkspaceContext ): UmbMediaPropertyDatasetContext { return new UmbMediaPropertyDatasetContext(host, this, variantId); } - - public override destroy(): void { - this.structure.destroy(); - this.#languageRepository.destroy(); - super.destroy(); - } } export { UmbMediaWorkspaceContext as api }; diff --git a/src/packages/media/media/workspace/views/info/media-workspace-view-info-reference.element.ts b/src/packages/media/media/workspace/views/info/media-workspace-view-info-reference.element.ts index 90169d6666..4f9aaa6bb8 100644 --- a/src/packages/media/media/workspace/views/info/media-workspace-view-info-reference.element.ts +++ b/src/packages/media/media/workspace/views/info/media-workspace-view-info-reference.element.ts @@ -1,7 +1,7 @@ +import { UmbMediaReferenceRepository } from '../../../reference/index.js'; import { css, customElement, html, nothing, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; import { isDefaultReference, isDocumentReference, isMediaReference } from '@umbraco-cms/backoffice/relations'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbMediaReferenceRepository } from '@umbraco-cms/backoffice/media'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; diff --git a/src/packages/media/media/workspace/views/info/media-workspace-view-info.element.ts b/src/packages/media/media/workspace/views/info/media-workspace-view-info.element.ts index 529d781978..2e49276698 100644 --- a/src/packages/media/media/workspace/views/info/media-workspace-view-info.element.ts +++ b/src/packages/media/media/workspace/views/info/media-workspace-view-info.element.ts @@ -1,3 +1,4 @@ +import { UMB_MEDIA_WORKSPACE_CONTEXT } from '../../media-workspace.context-token.js'; import { TimeOptions } from './utils.js'; import { css, customElement, html, ifDefined, nothing, repeat, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -5,7 +6,6 @@ import type { UmbMediaTypeItemModel } from '@umbraco-cms/backoffice/media-type'; import { UMB_MEDIA_TYPE_ENTITY_TYPE, UmbMediaTypeItemRepository } from '@umbraco-cms/backoffice/media-type'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { UMB_MEDIA_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/media'; import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; import type { MediaUrlInfoModel } from '@umbraco-cms/backoffice/external/backend-api'; diff --git a/src/packages/members/member-group/workspace/member-group-root/manifests.ts b/src/packages/members/member-group/workspace/member-group-root/manifests.ts index a066e19b59..ce7e83d833 100644 --- a/src/packages/members/member-group/workspace/member-group-root/manifests.ts +++ b/src/packages/members/member-group/workspace/member-group-root/manifests.ts @@ -1,7 +1,7 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_MEMBER_GROUP_COLLECTION_ALIAS } from '../../collection/manifests.js'; import { UMB_MEMBER_GROUP_ROOT_ENTITY_TYPE } from '../../entity.js'; import { UMB_MEMBER_GROUP_ROOT_WORKSPACE_ALIAS } from './constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/members/member-group/workspace/member-group/manifests.ts b/src/packages/members/member-group/workspace/member-group/manifests.ts index ebfd58fccb..c5811568f3 100644 --- a/src/packages/members/member-group/workspace/member-group/manifests.ts +++ b/src/packages/members/member-group/workspace/member-group/manifests.ts @@ -4,8 +4,7 @@ import type { ManifestWorkspaceActions, ManifestWorkspaceView, } from '@umbraco-cms/backoffice/workspace'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; +import { UmbSubmitWorkspaceAction, UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_MEMBER_GROUP_WORKSPACE_ALIAS = 'Umb.Workspace.MemberGroup'; diff --git a/src/packages/members/member-type/menu/manifests.ts b/src/packages/members/member-type/menu/manifests.ts index 39f0b5b1e5..85ca885488 100644 --- a/src/packages/members/member-type/menu/manifests.ts +++ b/src/packages/members/member-type/menu/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_MEMBER_TYPE_TREE_ALIAS } from '../tree/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/members/member-type/workspace/manifests.ts b/src/packages/members/member-type/workspace/manifests.ts index 1d9925c118..80d973d302 100644 --- a/src/packages/members/member-type/workspace/manifests.ts +++ b/src/packages/members/member-type/workspace/manifests.ts @@ -1,6 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_MEMBER_TYPE_COMPOSITION_REPOSITORY_ALIAS } from '../repository/index.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const UMB_MEMBER_TYPE_WORKSPACE_ALIAS = 'Umb.Workspace.MemberType'; diff --git a/src/packages/members/member/workspace/member-root/manifests.ts b/src/packages/members/member/workspace/member-root/manifests.ts index bfc4101ec5..6b3c12a77e 100644 --- a/src/packages/members/member/workspace/member-root/manifests.ts +++ b/src/packages/members/member/workspace/member-root/manifests.ts @@ -1,7 +1,7 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_MEMBER_COLLECTION_ALIAS } from '../../collection/manifests.js'; import { UMB_MEMBER_ROOT_ENTITY_TYPE } from '../../entity.js'; import { UMB_MEMBER_ROOT_WORKSPACE_ALIAS } from './constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/members/member/workspace/member/manifests.ts b/src/packages/members/member/workspace/member/manifests.ts index cb8be75248..aac2a61278 100644 --- a/src/packages/members/member/workspace/member/manifests.ts +++ b/src/packages/members/member/workspace/member/manifests.ts @@ -4,9 +4,8 @@ import type { ManifestWorkspaceActions, ManifestWorkspaceView, } from '@umbraco-cms/backoffice/workspace'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UmbSubmitWorkspaceAction, UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_CONTENT_HAS_PROPERTIES_WORKSPACE_CONDITION } from '@umbraco-cms/backoffice/content'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_MEMBER_WORKSPACE_ALIAS = 'Umb.Workspace.Member'; @@ -48,7 +47,7 @@ export const workspaceViews: Array = [ kind: 'contentEditor', alias: 'Umb.WorkspaceView.Member.Content', name: 'Member Workspace Content View', - weight: 100, + weight: 1000, meta: { label: '#general_details', pathname: 'content', @@ -69,10 +68,10 @@ export const workspaceViews: Array = [ alias: 'Umb.WorkspaceView.Member.Member', name: 'Member Workspace Member View', js: () => import('./views/member/member-workspace-view-member.element.js'), - weight: 200, + weight: 500, meta: { - label: '#treeHeaders_member', - pathname: 'member', + label: '#apps_umbInfo', + pathname: 'info', icon: 'icon-user', }, conditions: [ diff --git a/src/packages/members/member/workspace/member/member-workspace.context.ts b/src/packages/members/member/workspace/member/member-workspace.context.ts index 788afe2c1b..d21e65c1f2 100644 --- a/src/packages/members/member/workspace/member/member-workspace.context.ts +++ b/src/packages/members/member/workspace/member/member-workspace.context.ts @@ -1,186 +1,47 @@ -import { UmbMemberDetailRepository } from '../../repository/index.js'; -import type { - UmbMemberDetailModel, - UmbMemberValueModel, - UmbMemberVariantModel, - UmbMemberVariantOptionModel, -} from '../../types.js'; +import type { UmbMemberDetailRepository } from '../../repository/index.js'; +import { UMB_MEMBER_DETAIL_REPOSITORY_ALIAS } from '../../repository/index.js'; +import type { UmbMemberDetailModel, UmbMemberVariantModel } from '../../types.js'; import { UmbMemberPropertyDatasetContext } from '../../property-dataset-context/member-property-dataset-context.js'; import { UMB_MEMBER_ENTITY_TYPE, UMB_MEMBER_ROOT_ENTITY_TYPE } from '../../entity.js'; -import { sortVariants } from '../../utils.js'; -import { UMB_MEMBER_MANAGEMENT_SECTION_PATH } from '../../../section/index.js'; import { UMB_MEMBER_WORKSPACE_ALIAS } from './manifests.js'; import { UmbMemberWorkspaceEditorElement } from './member-workspace-editor.element.js'; import { UMB_MEMBER_DETAIL_MODEL_VARIANT_SCAFFOLD } from './constants.js'; import { UmbMemberTypeDetailRepository, type UmbMemberTypeDetailModel } from '@umbraco-cms/backoffice/member-type'; import { - UmbSubmittableWorkspaceContextBase, UmbWorkspaceIsNewRedirectController, UmbWorkspaceIsNewRedirectControllerAlias, - UmbWorkspaceSplitViewManager, } from '@umbraco-cms/backoffice/workspace'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { - UmbArrayState, - UmbObjectState, - appendToFrozenArray, - mergeObservables, -} from '@umbraco-cms/backoffice/observable-api'; -import { UmbContentTypeStructureManager } from '@umbraco-cms/backoffice/content-type'; -import { UMB_INVARIANT_CULTURE, UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import type { UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; -import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language'; -import type { UmbDataSourceResponse } from '@umbraco-cms/backoffice/repository'; -import { UmbContentWorkspaceDataManager, type UmbContentWorkspaceContext } from '@umbraco-cms/backoffice/content'; -import { UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; -import { UmbDataTypeItemRepositoryManager } from '@umbraco-cms/backoffice/data-type'; -import { map } from '@umbraco-cms/backoffice/external/rxjs'; -import { UmbEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity'; -import { - UmbRequestReloadChildrenOfEntityEvent, - UmbRequestReloadStructureForEntityEvent, -} from '@umbraco-cms/backoffice/entity-action'; -import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; +import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { UmbContentDetailWorkspaceContextBase, type UmbContentWorkspaceContext } from '@umbraco-cms/backoffice/content'; + +type ContentModel = UmbMemberDetailModel; +type ContentTypeModel = UmbMemberTypeDetailModel; -type EntityModel = UmbMemberDetailModel; export class UmbMemberWorkspaceContext - extends UmbSubmittableWorkspaceContextBase - implements UmbContentWorkspaceContext + extends UmbContentDetailWorkspaceContextBase< + ContentModel, + UmbMemberDetailRepository, + ContentTypeModel, + UmbMemberVariantModel + > + implements UmbContentWorkspaceContext { - public readonly IS_CONTENT_WORKSPACE_CONTEXT = true as const; - - public readonly repository = new UmbMemberDetailRepository(this); - - #parent = new UmbObjectState({ entityType: UMB_MEMBER_ROOT_ENTITY_TYPE, unique: null }); - readonly parentUnique = this.#parent.asObservablePart((parent) => (parent ? parent.unique : undefined)); - readonly parentEntityType = this.#parent.asObservablePart((parent) => (parent ? parent.entityType : undefined)); - - readonly #data = new UmbContentWorkspaceDataManager(this, UMB_MEMBER_DETAIL_MODEL_VARIANT_SCAFFOLD); - #getDataPromise?: Promise>; - - // TODO: Optimize this so it uses either a App Language Context or another somehow cached solution? [NL] - #languageRepository = new UmbLanguageCollectionRepository(this); - #languages = new UmbArrayState([], (x) => x.unique); - public readonly languages = this.#languages.asObservable(); - - public readonly readOnlyState = new UmbReadOnlyVariantStateManager(this); - - public isLoaded() { - return this.#getDataPromise; - } - - readonly data = this.#data.current; - readonly unique = this.#data.createObservablePartOfCurrent((data) => data?.unique); - readonly createDate = this.#data.createObservablePartOfCurrent((data) => data?.variants[0].createDate); - readonly updateDate = this.#data.createObservablePartOfCurrent((data) => data?.variants[0].updateDate); - readonly contentTypeUnique = this.#data.createObservablePartOfCurrent((data) => data?.memberType.unique); - - readonly variants = this.#data.createObservablePartOfCurrent((data) => data?.variants ?? []); - - readonly values = this.#data.createObservablePartOfCurrent((data) => data?.values); - getValues() { - return this.#data.getCurrent()?.values; - } - - readonly structure = new UmbContentTypeStructureManager(this, new UmbMemberTypeDetailRepository(this)); - readonly variesByCulture = this.structure.ownerContentTypeObservablePart((x) => x?.variesByCulture); - readonly variesBySegment = this.structure.ownerContentTypeObservablePart((x) => x?.variesBySegment); - readonly varies = this.structure.ownerContentTypeObservablePart((x) => - x ? x.variesByCulture || x.variesBySegment : undefined, - ); - #varies?: boolean; - #variesByCulture?: boolean; - #variesBySegment?: boolean; - - readonly kind = this.#data.createObservablePartOfCurrent((data) => data?.kind); - - readonly #dataTypeItemManager = new UmbDataTypeItemRepositoryManager(this); - #dataTypeSchemaAliasMap = new Map(); - - readonly splitView = new UmbWorkspaceSplitViewManager(); - - readonly variantOptions = mergeObservables( - [this.varies, this.variants, this.languages], - ([varies, variants, languages]) => { - // TODO: When including segments, when be aware about the case of segment varying when not culture varying. [NL] - if (varies === true) { - return languages.map((language) => { - return { - variant: variants.find((x) => x.culture === language.unique), - language, - // TODO: When including segments, this object should be updated to include a object for the segment. [NL] - // TODO: When including segments, the unique should be updated to include the segment as well. [NL] - unique: language.unique, // This must be a variantId string! - culture: language.unique, - segment: null, - } as UmbMemberVariantOptionModel; - }); - } else if (varies === false) { - return [ - { - variant: variants.find((x) => x.culture === null), - language: languages.find((x) => x.isDefault), - culture: null, - segment: null, - unique: UMB_INVARIANT_CULTURE, // This must be a variantId string! - } as UmbMemberVariantOptionModel, - ]; - } - return [] as Array; - }, - ).pipe(map((results) => results.sort(sortVariants))); - - // TODO: this should be set up for all entity workspace contexts in a base class - #entityContext = new UmbEntityContext(this); + readonly contentTypeUnique = this._data.createObservablePartOfCurrent((data) => data?.memberType.unique); + readonly kind = this._data.createObservablePartOfCurrent((data) => data?.kind); + readonly createDate = this._data.createObservablePartOfCurrent((data) => data?.variants[0].createDate); + readonly updateDate = this._data.createObservablePartOfCurrent((data) => data?.variants[0].updateDate); constructor(host: UmbControllerHost) { - super(host, UMB_MEMBER_WORKSPACE_ALIAS); + super(host, { + entityType: UMB_MEMBER_ENTITY_TYPE, + workspaceAlias: UMB_MEMBER_WORKSPACE_ALIAS, + detailRepositoryAlias: UMB_MEMBER_DETAIL_REPOSITORY_ALIAS, + contentTypeDetailRepository: UmbMemberTypeDetailRepository, + contentVariantScaffold: UMB_MEMBER_DETAIL_MODEL_VARIANT_SCAFFOLD, + }); this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique), null); - this.observe( - this.varies, - (varies) => { - this.#data.setVaries(varies); - this.#varies = varies; - }, - null, - ); - this.observe( - this.variesByCulture, - (varies) => { - this.#data.setVariesByCulture(varies); - this.#variesByCulture = varies; - }, - null, - ); - this.observe( - this.variesBySegment, - (varies) => { - this.#data.setVariesBySegment(varies); - this.#variesBySegment = varies; - }, - null, - ); - this.observe( - this.structure.contentTypeDataTypeUniques, - (dataTypeUniques: Array) => { - this.#dataTypeItemManager.setUniques(dataTypeUniques); - }, - null, - ); - this.observe( - this.#dataTypeItemManager.items, - (dataTypes) => { - // Make a map of the data type unique and editorAlias: - this.#dataTypeSchemaAliasMap = new Map( - dataTypes.map((dataType) => { - return [dataType.unique, dataType.propertyEditorSchemaAlias]; - }), - ); - }, - null, - ); - this.loadLanguages(); this.routes.setRoutes([ { @@ -209,241 +70,34 @@ export class UmbMemberWorkspaceContext ]); } - override resetState() { - super.resetState(); - this.#data.clear(); - } - - async loadLanguages() { - // TODO: If we don't end up having a Global Context for languages, then we should at least change this into using a asObservable which should be returned from the repository. [Nl] - const { data } = await this.#languageRepository.requestCollection({}); - this.#languages.setValue(data?.items ?? []); - } - - async load(unique: string) { - this.resetState(); - this.#getDataPromise = this.repository.requestByUnique(unique); - type GetDataType = Awaited>; - const { data, asObservable } = (await this.#getDataPromise) as GetDataType; - - if (data) { - this.#entityContext.setEntityType(UMB_MEMBER_ENTITY_TYPE); - this.#entityContext.setUnique(unique); - this.setIsNew(false); - this.#data.setPersisted(data); - this.#data.setCurrent(data); - } - - this.observe(asObservable(), (member) => this.#onMemberStoreChange(member), 'umbMemberStoreObserver'); - } - - #onMemberStoreChange(member: EntityModel | undefined) { - if (!member) { - //TODO: This solution is alright for now. But reconsider when we introduce signal-r - history.pushState(null, '', UMB_MEMBER_MANAGEMENT_SECTION_PATH); - } - } - async create(memberTypeUnique: string) { - this.resetState(); - this.#getDataPromise = this.repository.createScaffold({ - memberType: { - unique: memberTypeUnique, + return this.createScaffold({ + parent: { entityType: UMB_MEMBER_ROOT_ENTITY_TYPE, unique: null }, + preset: { + memberType: { + unique: memberTypeUnique, + }, }, }); - const { data } = await this.#getDataPromise; - if (!data) return undefined; - - this.#entityContext.setEntityType(UMB_MEMBER_ENTITY_TYPE); - this.#entityContext.setUnique(data.unique); - this.setIsNew(true); - this.#data.setPersisted(undefined); - this.#data.setCurrent(data); - return data; - } - - getData() { - return this.#data.getCurrent(); - } - - getUnique() { - return this.getData()?.unique; - } - - getEntityType() { - return UMB_MEMBER_ENTITY_TYPE; - } - - getContentTypeId() { - return this.getData()?.memberType.unique; - } - - getVaries() { - return this.#varies; - } - getVariesByCulture() { - return this.#variesByCulture; - } - getVariesBySegment() { - return this.#variesBySegment; - } - - variantById(variantId: UmbVariantId) { - return this.#data.createObservablePartOfCurrent((data) => data?.variants?.find((x) => variantId.compare(x))); - } - - getVariant(variantId: UmbVariantId) { - return this.#data.getCurrent()?.variants?.find((x) => variantId.compare(x)); - } - - getName(variantId?: UmbVariantId) { - const variants = this.#data.getCurrent()?.variants; - if (!variants) return; - if (variantId) { - return variants.find((x) => variantId.compare(x))?.name; - } else { - return variants[0]?.name; - } - } - - setName(name: string, variantId?: UmbVariantId) { - this.#data.updateVariantData(variantId ?? UmbVariantId.CreateInvariant(), { name }); - } - - name(variantId?: UmbVariantId) { - return this.#data.createObservablePartOfCurrent( - (data) => data?.variants?.find((x) => variantId?.compare(x))?.name ?? '', - ); - } - - async propertyStructureById(propertyId: string) { - return this.structure.propertyStructureById(propertyId); } /** - * @function propertyValueByAlias - * @param {string} propertyAlias - property alias to observe - * @param {UmbVariantId} variantId - variant identifier for the value to observe - * @returns {Promise | undefined>} - * @description Get an Observable for the value of this property. + * Gets the unique identifier of the content type. + * @deprecated Use `getContentTypeUnique` instead. + * @returns { string | undefined} The unique identifier of the content type. + * @memberof UmbMemberWorkspaceContext */ - async propertyValueByAlias(propertyAlias: string, variantId?: UmbVariantId) { - return this.#data.createObservablePartOfCurrent( - (data) => - data?.values?.find((x) => x?.alias === propertyAlias && (variantId ? variantId.compare(x) : true)) - ?.value as PropertyValueType, - ); + getContentTypeId(): string | undefined { + return this.getContentTypeUnique(); } /** - * Get the current value of the property with the given alias and variantId. - * @param {string} alias - property alias to set. - * @param {UmbVariantId} variantId - variant identifier for this value to be defined for. - * @returns {ReturnType | undefined}The value or undefined if not set or found. + * Gets the unique identifier of the content type. + * @returns { string | undefined} The unique identifier of the content type. + * @memberof UmbMemberWorkspaceContext */ - getPropertyValue(alias: string, variantId?: UmbVariantId) { - const currentData = this.getData(); - if (currentData) { - const newDataSet = currentData.values?.find( - (x) => x.alias === alias && (variantId ? variantId.compare(x) : true), - ); - return newDataSet?.value as ReturnType; - } - return undefined; - } - async setPropertyValue(alias: string, value: ValueType, variantId?: UmbVariantId) { - this.initiatePropertyValueChange(); - variantId ??= UmbVariantId.CreateInvariant(); - const property = await this.structure.getPropertyStructureByAlias(alias); - - if (!property) { - throw new Error(`Property alias "${alias}" not found.`); - } - - const editorAlias = this.#dataTypeSchemaAliasMap.get(property.dataType.unique); - if (!editorAlias) { - throw new Error(`Editor Alias of "${property.dataType.unique}" not found.`); - } - - const entry = { ...variantId.toObject(), alias, editorAlias, value } as UmbMemberValueModel; - - const currentData = this.getData(); - if (currentData) { - const values = appendToFrozenArray( - currentData.values ?? [], - entry, - (x) => x.alias === alias && variantId!.compare(x), - ); - this.#data.updateCurrent({ values }); - - // TODO: We should move this type of logic to the act of saving [NL] - this.#data.ensureVariantData(variantId); - } - this.finishPropertyValueChange(); - } - - initiatePropertyValueChange() { - this.#data.initiatePropertyValueChange(); - } - finishPropertyValueChange = () => { - this.#data.finishPropertyValueChange(); - }; - - async #handleSave() { - const current = this.#data.getCurrent(); - if (!current) throw new Error('Data is missing'); - if (!current.unique) throw new Error('Unique is missing'); - - if (this.getIsNew()) { - // Create: - const parent = this.#parent.getValue(); - if (!parent) throw new Error('Parent is not set'); - - const { data, error } = await this.repository.create(current); - if (!data || error) { - throw new Error('Could not create member.'); - } - - this.setIsNew(false); - this.#data.setPersisted(data); - // TODO: Missing variant data filtering. - this.#data.setCurrent(data); - - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadChildrenOfEntityEvent({ - entityType: parent.entityType, - unique: parent.unique, - }); - eventContext.dispatchEvent(event); - } else { - // Save: - const { data, error } = await this.repository.save(current); - if (!data || error) { - throw new Error('Could not update member.'); - } - this.#data.setPersisted(data); - // TODO: Missing variant data filtering. - this.#data.setCurrent(data); - - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadStructureForEntityEvent({ - entityType: this.getEntityType(), - unique: this.getUnique()!, - }); - - eventContext.dispatchEvent(event); - } - } - - async submit() { - return this.#handleSave(); - } - - async delete() { - const id = this.getUnique(); - if (id) { - await this.repository.delete(id); - } + getContentTypeUnique(): string | undefined { + return this.getData()?.memberType.unique; } public createPropertyDatasetContext( @@ -453,22 +107,18 @@ export class UmbMemberWorkspaceContext return new UmbMemberPropertyDatasetContext(host, this, variantId); } - public override destroy(): void { - super.destroy(); - } - set( propertyName: PropertyName, value: UmbMemberDetailModel[PropertyName], ) { - this.#data.updateCurrent({ [propertyName]: value }); + this._data.updateCurrent({ [propertyName]: value }); } // Only for CRUD demonstration purposes - updateData(data: Partial) { - const currentData = this.#data.getCurrent(); + updateData(data: Partial) { + const currentData = this._data.getCurrent(); if (!currentData) throw new Error('No data to update'); - this.#data.setCurrent({ ...currentData, ...data }); + this._data.setCurrent({ ...currentData, ...data }); } get email(): string { @@ -512,7 +162,7 @@ export class UmbMemberWorkspaceContext } #get(propertyName: PropertyName) { - return this.#data.getCurrent()?.[propertyName]; + return this._data.getCurrent()?.[propertyName]; } } diff --git a/src/packages/property-editors/collection/manifests.ts b/src/packages/property-editors/collection/manifests.ts index ae597d3610..eb32c743c8 100644 --- a/src/packages/property-editors/collection/manifests.ts +++ b/src/packages/property-editors/collection/manifests.ts @@ -50,19 +50,19 @@ const propertyEditorUiManifest: ManifestPropertyEditorUi = { { alias: 'icon', label: 'Workspace View icon', - description: 'The icon for the Collection\'s Workspace View.', + description: "The icon for the Collection's Workspace View.", propertyEditorUiAlias: 'Umb.PropertyEditorUi.IconPicker', }, { alias: 'tabName', label: 'Workspace View name', - description: 'The name of the Collection\'s Workspace View (default if empty: Child Items).', + description: "The name of the Collection's Workspace View (default if empty: Child Items).", propertyEditorUiAlias: 'Umb.PropertyEditorUi.TextBox', }, { alias: 'showContentFirst', label: 'Show Content Workspace View First', - description: 'Enable this to show the Content Workspace View by default instead of the Collection\'s.', + description: "Enable this to show the Content Workspace View by default instead of the Collection's.", propertyEditorUiAlias: 'Umb.PropertyEditorUi.Toggle', }, ], diff --git a/src/packages/templating/partial-views/menu/manifests.ts b/src/packages/templating/partial-views/menu/manifests.ts index de2147b6f0..414875d9d6 100644 --- a/src/packages/templating/partial-views/menu/manifests.ts +++ b/src/packages/templating/partial-views/menu/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_PARTIAL_VIEW_TREE_ALIAS } from '../tree/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/templating/partial-views/workspace/manifests.ts b/src/packages/templating/partial-views/workspace/manifests.ts index 7e21cc0506..4ab987402a 100644 --- a/src/packages/templating/partial-views/workspace/manifests.ts +++ b/src/packages/templating/partial-views/workspace/manifests.ts @@ -1,5 +1,4 @@ -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; +import { UmbSubmitWorkspaceAction, UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_PARTIAL_VIEW_WORKSPACE_ALIAS = 'Umb.Workspace.PartialView'; diff --git a/src/packages/templating/scripts/menu/manifests.ts b/src/packages/templating/scripts/menu/manifests.ts index 9dd63daaa8..f4e55324a8 100644 --- a/src/packages/templating/scripts/menu/manifests.ts +++ b/src/packages/templating/scripts/menu/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_SCRIPT_TREE_ALIAS } from '../tree/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_SCRIPT_MENU_ITEM_ALIAS = 'Umb.MenuItem.Script'; export const manifests: Array = [ diff --git a/src/packages/templating/scripts/workspace/manifests.ts b/src/packages/templating/scripts/workspace/manifests.ts index 13664d8a9c..ad7af55c21 100644 --- a/src/packages/templating/scripts/workspace/manifests.ts +++ b/src/packages/templating/scripts/workspace/manifests.ts @@ -1,6 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_SCRIPT_ENTITY_TYPE } from '../entity.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const UMB_SCRIPT_WORKSPACE_ALIAS = 'Umb.Workspace.Script'; export const UMB_SAVE_SCRIPT_WORKSPACE_ACTION_ALIAS = 'Umb.WorkspaceAction.Script.Save'; diff --git a/src/packages/templating/stylesheets/menu/manifests.ts b/src/packages/templating/stylesheets/menu/manifests.ts index 1a74c5f0f1..1b93c439a8 100644 --- a/src/packages/templating/stylesheets/menu/manifests.ts +++ b/src/packages/templating/stylesheets/menu/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_STYLESHEET_TREE_ALIAS } from '../tree/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/templating/stylesheets/workspace/manifests.ts b/src/packages/templating/stylesheets/workspace/manifests.ts index 98911276c1..ee593b786d 100644 --- a/src/packages/templating/stylesheets/workspace/manifests.ts +++ b/src/packages/templating/stylesheets/workspace/manifests.ts @@ -1,5 +1,4 @@ -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; +import { UmbSubmitWorkspaceAction, UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_STYLESHEET_WORKSPACE_ALIAS = 'Umb.Workspace.Stylesheet'; diff --git a/src/packages/templating/templates/menu/manifests.ts b/src/packages/templating/templates/menu/manifests.ts index ab40f89560..3c63d3fe2b 100644 --- a/src/packages/templating/templates/menu/manifests.ts +++ b/src/packages/templating/templates/menu/manifests.ts @@ -1,5 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_TEMPLATE_TREE_ALIAS } from '../tree/index.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/templating/templates/workspace/manifests.ts b/src/packages/templating/templates/workspace/manifests.ts index fc2b33e8ce..8f9c84b800 100644 --- a/src/packages/templating/templates/workspace/manifests.ts +++ b/src/packages/templating/templates/workspace/manifests.ts @@ -1,5 +1,4 @@ -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; +import { UmbSubmitWorkspaceAction, UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_TEMPLATE_WORKSPACE_ALIAS = 'Umb.Workspace.Template'; diff --git a/src/packages/ufm/components/content-name/content-name.component.ts b/src/packages/ufm/components/content-name/content-name.component.ts index 49a84361e7..460befde95 100644 --- a/src/packages/ufm/components/content-name/content-name.component.ts +++ b/src/packages/ufm/components/content-name/content-name.component.ts @@ -9,8 +9,8 @@ export class UmbUfmContentNameComponent extends UmbUfmComponentBase { if (token.prefix === '~') { /* - * @deprecated since version 15.0-rc3 - */ + * @deprecated since version 15.0-rc3 + */ console.warn(`Please update your UFM syntax from \`${token.raw}\` to \`{umbContentName:${token.text}}\`.`); } diff --git a/src/packages/user/user-group/workspace/user-group-root/manifests.ts b/src/packages/user/user-group/workspace/user-group-root/manifests.ts index 0d528f9fc7..6291ed199a 100644 --- a/src/packages/user/user-group/workspace/user-group-root/manifests.ts +++ b/src/packages/user/user-group/workspace/user-group-root/manifests.ts @@ -1,7 +1,7 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_USER_GROUP_COLLECTION_ALIAS } from '../../collection/index.js'; import { UMB_USER_GROUP_ROOT_ENTITY_TYPE } from '../../entity.js'; import { UMB_USER_GROUP_WORKSPACE_ALIAS } from './constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/user/user-group/workspace/user-group/manifests.ts b/src/packages/user/user-group/workspace/user-group/manifests.ts index 3a2e096501..4738e15e07 100644 --- a/src/packages/user/user-group/workspace/user-group/manifests.ts +++ b/src/packages/user/user-group/workspace/user-group/manifests.ts @@ -1,5 +1,4 @@ -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; +import { UmbSubmitWorkspaceAction, UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_USER_GROUP_WORKSPACE_ALIAS = 'Umb.Workspace.UserGroup'; diff --git a/src/packages/user/user/workspace/user-root/manifests.ts b/src/packages/user/user/workspace/user-root/manifests.ts index 44217e9ba9..a4e5970e31 100644 --- a/src/packages/user/user/workspace/user-root/manifests.ts +++ b/src/packages/user/user/workspace/user-root/manifests.ts @@ -1,7 +1,7 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_USER_COLLECTION_ALIAS } from '../../collection/constants.js'; import { UMB_USER_ROOT_ENTITY_TYPE } from '../../entity.js'; import { UMB_USER_ROOT_WORKSPACE_ALIAS } from './constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/user/user/workspace/user/manifests.ts b/src/packages/user/user/workspace/user/manifests.ts index 1149050381..850cee09f9 100644 --- a/src/packages/user/user/workspace/user/manifests.ts +++ b/src/packages/user/user/workspace/user/manifests.ts @@ -1,7 +1,6 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_USER_ENTITY_TYPE } from '../../entity.js'; import { UMB_USER_WORKSPACE_ALIAS } from './constants.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/webhook/workspace/webhook-root/manifests.ts b/src/packages/webhook/workspace/webhook-root/manifests.ts index c837a613e0..4ce0a1b821 100644 --- a/src/packages/webhook/workspace/webhook-root/manifests.ts +++ b/src/packages/webhook/workspace/webhook-root/manifests.ts @@ -1,7 +1,7 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_WEBHOOK_COLLECTION_ALIAS } from '../../collection/manifests.js'; import { UMB_WEBHOOK_ROOT_ENTITY_TYPE } from '../../entity.js'; import { UMB_WEBHOOK_ROOT_WORKSPACE_ALIAS } from './constants.js'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { diff --git a/src/packages/webhook/workspace/webhook/manifests.ts b/src/packages/webhook/workspace/webhook/manifests.ts index ae338f664e..da58546d92 100644 --- a/src/packages/webhook/workspace/webhook/manifests.ts +++ b/src/packages/webhook/workspace/webhook/manifests.ts @@ -1,6 +1,5 @@ -import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_WEBHOOK_ENTITY_TYPE, UMB_WEBHOOK_WORKSPACE_ALIAS } from '../../entity.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ {