diff --git a/src/Umbraco.Web.UI.Client/.storybook/preview.js b/src/Umbraco.Web.UI.Client/.storybook/preview.js index 11eaeaa8ed..b256b970b6 100644 --- a/src/Umbraco.Web.UI.Client/.storybook/preview.js +++ b/src/Umbraco.Web.UI.Client/.storybook/preview.js @@ -11,11 +11,11 @@ import { html } from 'lit-html'; import { initialize, mswDecorator } from 'msw-storybook-addon'; import { setCustomElements } from '@storybook/web-components'; -import { UmbDataTypeStore } from '../src/backoffice/settings/data-types/data-type.store'; +import { UMB_DATA_TYPE_STORE_CONTEXT_TOKEN, UmbDataTypeStore } from '../src/backoffice/settings/data-types/repository/data-type.store.ts'; import { - UMB_DOCUMENT_TYPE_DETAIL_STORE_CONTEXT_TOKEN, + UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN, UmbDocumentTypeStore, -} from '../src/backoffice/documents/document-types/document-type.detail.store'; +} from '../src/backoffice/documents/document-types/repository/document-type.store.ts'; import customElementManifests from '../custom-elements.json'; import { UmbIconStore } from '../libs/store/icon/icon.store'; @@ -69,7 +69,7 @@ const dataTypeStoreProvider = (story) => html` const documentTypeStoreProvider = (story) => html` new UmbDocumentTypeStore(host)} >${story()} diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ConstructorInfoModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ConstructorInfoModel.ts index 79fff3395a..4ebd9fe2f8 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ConstructorInfoModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ConstructorInfoModel.ts @@ -45,4 +45,3 @@ export type ConstructorInfoModel = { readonly isSecurityTransparent?: boolean; memberType?: MemberTypesModel; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/CultureModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/CultureModel.ts index 8a6836bd00..108c1716fb 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/CultureModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/CultureModel.ts @@ -6,4 +6,3 @@ export type CultureModel = { name?: string; englishName?: string; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DataTypeModelBaseModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DataTypeModelBaseModel.ts index 85a98bebfb..c1a5927f3f 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DataTypeModelBaseModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DataTypeModelBaseModel.ts @@ -10,4 +10,3 @@ export type DataTypeModelBaseModel = { propertyEditorUiAlias?: string | null; data?: Array; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DataTypePropertyModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DataTypePropertyModel.ts index 89e844f606..047bf4db9a 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DataTypePropertyModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DataTypePropertyModel.ts @@ -6,4 +6,3 @@ export type DataTypePropertyModel = { alias?: string; value?: any; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DataTypePropertyReferenceModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DataTypePropertyReferenceModel.ts index f891dd3af0..d78ee8d9f8 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DataTypePropertyReferenceModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DataTypePropertyReferenceModel.ts @@ -6,4 +6,3 @@ export type DataTypePropertyReferenceModel = { name?: string; alias?: string; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DatabaseInstallModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DatabaseInstallModel.ts index fca252ff44..c0251aaca4 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DatabaseInstallModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DatabaseInstallModel.ts @@ -12,4 +12,3 @@ export type DatabaseInstallModel = { useIntegratedAuthentication?: boolean; connectionString?: string | null; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DatabaseSettingsModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DatabaseSettingsModel.ts index 1dd2cfcf83..a56f73e8f8 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DatabaseSettingsModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DatabaseSettingsModel.ts @@ -15,4 +15,3 @@ export type DatabaseSettingsModel = { supportsIntegratedAuthentication?: boolean; requiresConnectionTest?: boolean; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DictionaryItemModelBaseModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DictionaryItemModelBaseModel.ts index cd1a89d95a..1ffac7af64 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DictionaryItemModelBaseModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DictionaryItemModelBaseModel.ts @@ -8,4 +8,3 @@ export type DictionaryItemModelBaseModel = { name?: string; translations?: Array; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DictionaryItemTranslationModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DictionaryItemTranslationModel.ts index 9bc10a302f..37e5189a01 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DictionaryItemTranslationModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DictionaryItemTranslationModel.ts @@ -6,4 +6,3 @@ export type DictionaryItemTranslationModel = { isoCode?: string; translation?: string; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DictionaryItemsImportModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DictionaryItemsImportModel.ts index 73d0d2c31e..5c343d7acd 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DictionaryItemsImportModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DictionaryItemsImportModel.ts @@ -7,4 +7,3 @@ export type DictionaryItemsImportModel = { name?: string | null; parentKey?: string | null; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DictionaryOverviewModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DictionaryOverviewModel.ts index 9955664ba7..93a7adc20b 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DictionaryOverviewModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/DictionaryOverviewModel.ts @@ -8,4 +8,3 @@ export type DictionaryOverviewModel = { parentKey?: string | null; translatedIsoCodes?: Array; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/FieldInfoModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/FieldInfoModel.ts index e13619509a..0c42eb5b24 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/FieldInfoModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/FieldInfoModel.ts @@ -37,4 +37,3 @@ export type FieldInfoModel = { readonly isSecurityTransparent?: boolean; fieldHandle?: RuntimeFieldHandleModel; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/FieldModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/FieldModel.ts index 0ef3d38779..8c1ed34bac 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/FieldModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/FieldModel.ts @@ -6,4 +6,3 @@ export type FieldModel = { name?: string; values?: Array; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/HealthCheckActionModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/HealthCheckActionModel.ts index 82f2cd7448..f65c2badc2 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/HealthCheckActionModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/HealthCheckActionModel.ts @@ -12,4 +12,3 @@ export type HealthCheckActionModel = { providedValueValidation?: string | null; providedValueValidationRegex?: string | null; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/HelpPageModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/HelpPageModel.ts index 7a56b0085e..7cb3a00356 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/HelpPageModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/HelpPageModel.ts @@ -8,4 +8,3 @@ export type HelpPageModel = { url?: string | null; type?: string | null; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/IndexModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/IndexModel.ts index a9aec87082..80bdbecaa6 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/IndexModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/IndexModel.ts @@ -13,4 +13,3 @@ export type IndexModel = { fieldCount: number; providerProperties?: Record | null; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/IntPtrModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/IntPtrModel.ts index f0e4a06a5f..b487941c8d 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/IntPtrModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/IntPtrModel.ts @@ -4,4 +4,3 @@ export type IntPtrModel = { }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/JsonNamingPolicyModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/JsonNamingPolicyModel.ts index e29b3cbf75..bc0f7667c5 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/JsonNamingPolicyModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/JsonNamingPolicyModel.ts @@ -4,4 +4,3 @@ export type JsonNamingPolicyModel = { }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/LanguageModelBaseModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/LanguageModelBaseModel.ts index eaff3e8d4e..b7dd8054d4 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/LanguageModelBaseModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/LanguageModelBaseModel.ts @@ -8,4 +8,3 @@ export type LanguageModelBaseModel = { isMandatory?: boolean; fallbackIsoCode?: string | null; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/LogMessagePropertyModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/LogMessagePropertyModel.ts index 098e1d9228..0f2444b41c 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/LogMessagePropertyModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/LogMessagePropertyModel.ts @@ -6,4 +6,3 @@ export type LogMessagePropertyModel = { name?: string; value?: string | null; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/LogTemplateModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/LogTemplateModel.ts index 30919c8239..2d821bf05b 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/LogTemplateModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/LogTemplateModel.ts @@ -6,4 +6,3 @@ export type LogTemplateModel = { messageTemplate?: string | null; count?: number; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/MethodBaseModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/MethodBaseModel.ts index 7191b68ab8..9212e93f38 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/MethodBaseModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/MethodBaseModel.ts @@ -45,4 +45,3 @@ export type MethodBaseModel = { readonly isSecuritySafeCritical?: boolean; readonly isSecurityTransparent?: boolean; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ModelsBuilderModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ModelsBuilderModel.ts index 745bba54df..4da279fbc9 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ModelsBuilderModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ModelsBuilderModel.ts @@ -13,4 +13,3 @@ export type ModelsBuilderModel = { modelsNamespace?: string | null; trackingOutOfDateModels?: boolean; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ModuleHandleModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ModuleHandleModel.ts index 6af7d0babe..44341ef114 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ModuleHandleModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ModuleHandleModel.ts @@ -5,4 +5,3 @@ export type ModuleHandleModel = { readonly mdStreamVersion?: number; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/NotFoundResultModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/NotFoundResultModel.ts index 7f1bcdfcc7..54b3b787b7 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/NotFoundResultModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/NotFoundResultModel.ts @@ -5,4 +5,3 @@ export type NotFoundResultModel = { statusCode?: number; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/OkResultModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/OkResultModel.ts index 886e643b17..0b041f81ee 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/OkResultModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/OkResultModel.ts @@ -5,4 +5,3 @@ export type OkResultModel = { statusCode?: number; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ProfilingStatusModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ProfilingStatusModel.ts index b2a7cc321c..1e7ba7fbdd 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ProfilingStatusModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ProfilingStatusModel.ts @@ -5,4 +5,3 @@ export type ProfilingStatusModel = { enabled?: boolean; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/RecycleBinItemModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/RecycleBinItemModel.ts index a0b0799535..181e63500a 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/RecycleBinItemModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/RecycleBinItemModel.ts @@ -11,4 +11,3 @@ export type RecycleBinItemModel = { isContainer?: boolean; parentKey?: string | null; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/RedirectUrlModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/RedirectUrlModel.ts index fe39dcab56..73eb0b7b82 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/RedirectUrlModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/RedirectUrlModel.ts @@ -10,4 +10,3 @@ export type RedirectUrlModel = { contentKey?: string; culture?: string | null; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ReferenceHandlerModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ReferenceHandlerModel.ts index c53808f1d0..4b15114d89 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ReferenceHandlerModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/ReferenceHandlerModel.ts @@ -4,4 +4,3 @@ export type ReferenceHandlerModel = { }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/RelationItemModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/RelationItemModel.ts index aee9a307a8..281b2d1263 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/RelationItemModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/RelationItemModel.ts @@ -13,4 +13,3 @@ export type RelationItemModel = { relationTypeIsBidirectional?: boolean; relationTypeIsDependency?: boolean; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/RelationModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/RelationModel.ts index 47de4a65d7..4a3608d699 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/RelationModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/RelationModel.ts @@ -10,4 +10,3 @@ export type RelationModel = { createDate?: string; comment?: string | null; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/SavedLogSearchModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/SavedLogSearchModel.ts index f75781f02a..3f896f2bde 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/SavedLogSearchModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/SavedLogSearchModel.ts @@ -6,4 +6,3 @@ export type SavedLogSearchModel = { name?: string; query?: string; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/SearchResultModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/SearchResultModel.ts index 82f9cf64de..9bfc241427 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/SearchResultModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/SearchResultModel.ts @@ -10,4 +10,3 @@ export type SearchResultModel = { readonly fieldCount?: number; fields?: Array; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/SearcherModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/SearcherModel.ts index e20520f4d6..3183930925 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/SearcherModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/SearcherModel.ts @@ -5,4 +5,3 @@ export type SearcherModel = { name?: string; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateModelBaseModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateModelBaseModel.ts index 7471d8b826..11e0260809 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateModelBaseModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateModelBaseModel.ts @@ -7,4 +7,3 @@ export type TemplateModelBaseModel = { alias?: string; content?: string | null; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateQueryExecuteFilterModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateQueryExecuteFilterModel.ts index 1befb86885..30144b509e 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateQueryExecuteFilterModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateQueryExecuteFilterModel.ts @@ -9,4 +9,3 @@ export type TemplateQueryExecuteFilterModel = { constraintValue?: string; operator?: OperatorModel; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateQueryExecuteModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateQueryExecuteModel.ts index 029409b196..2185b53cab 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateQueryExecuteModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateQueryExecuteModel.ts @@ -12,4 +12,3 @@ export type TemplateQueryExecuteModel = { sort?: TemplateQueryExecuteSortModel | null; take?: number; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateQueryExecuteSortModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateQueryExecuteSortModel.ts index d3a1b6cf56..a733632dcc 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateQueryExecuteSortModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateQueryExecuteSortModel.ts @@ -6,4 +6,3 @@ export type TemplateQueryExecuteSortModel = { propertyAlias?: string; direction?: string | null; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateQueryResultItemModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateQueryResultItemModel.ts index 43d24a4c62..3292b1329d 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateQueryResultItemModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateQueryResultItemModel.ts @@ -6,4 +6,3 @@ export type TemplateQueryResultItemModel = { icon?: string; name?: string; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateScaffoldModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateScaffoldModel.ts index 47040df1dc..9ecf6e1bc7 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateScaffoldModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TemplateScaffoldModel.ts @@ -5,4 +5,3 @@ export type TemplateScaffoldModel = { content?: string; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TreeItemModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TreeItemModel.ts index b16725118d..05e948e18a 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TreeItemModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TreeItemModel.ts @@ -8,4 +8,3 @@ export type TreeItemModel = { icon?: string; hasChildren?: boolean; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TypeInfoModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TypeInfoModel.ts index 1d943977ee..1088581578 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TypeInfoModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TypeInfoModel.ts @@ -101,4 +101,3 @@ export type TypeInfoModel = { readonly declaredProperties?: Array; readonly implementedInterfaces?: Array; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TypeModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TypeModel.ts index 235e41a3fa..9574c793a0 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TypeModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/TypeModel.ts @@ -86,4 +86,3 @@ export type TypeModel = { readonly containsGenericParameters?: boolean; readonly isVisible?: boolean; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/UpgradeSettingsModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/UpgradeSettingsModel.ts index 0486d96ff8..1219327f31 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/UpgradeSettingsModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/UpgradeSettingsModel.ts @@ -9,4 +9,3 @@ export type UpgradeSettingsModel = { oldVersion?: string; readonly reportUrl?: string; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/UserInstallModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/UserInstallModel.ts index 9c89afe57b..d796200d4a 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/UserInstallModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/UserInstallModel.ts @@ -8,4 +8,3 @@ export type UserInstallModel = { password: string; readonly subscribeToNewsletter?: boolean; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/UserSettingsModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/UserSettingsModel.ts index af052a9cc7..7ee6b138b1 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/UserSettingsModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/UserSettingsModel.ts @@ -9,4 +9,3 @@ export type UserSettingsModel = { minNonAlphaNumericLength?: number; consentLevels?: Array; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/VersionModel.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/VersionModel.ts index a4405a253b..f4ce6e5491 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/VersionModel.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/models/VersionModel.ts @@ -5,4 +5,3 @@ export type VersionModel = { version?: string; }; - diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/DataTypeResource.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/DataTypeResource.ts index 2fbd392e9f..7ac7d9a0fb 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/DataTypeResource.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/DataTypeResource.ts @@ -25,10 +25,10 @@ export class DataTypeResource { * @throws ApiError */ public static postDataType({ - requestBody, - }: { - requestBody?: DataTypeCreateModel, - }): CancelablePromise { +requestBody, +}: { +requestBody?: DataTypeCreateModel, +}): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/umbraco/management/api/v1/data-type', @@ -67,10 +67,10 @@ export class DataTypeResource { * @throws ApiError */ public static deleteDataTypeByKey({ - key, - }: { - key: string, - }): CancelablePromise { +key, +}: { +key: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/umbraco/management/api/v1/data-type/{key}', @@ -89,12 +89,12 @@ export class DataTypeResource { * @throws ApiError */ public static putDataTypeByKey({ - key, - requestBody, - }: { - key: string, - requestBody?: DataTypeUpdateModel, - }): CancelablePromise { +key, +requestBody, +}: { +key: string, +requestBody?: DataTypeUpdateModel, +}): CancelablePromise { return __request(OpenAPI, { method: 'PUT', url: '/umbraco/management/api/v1/data-type/{key}', @@ -186,10 +186,10 @@ export class DataTypeResource { * @throws ApiError */ public static postDataTypeFolder({ - requestBody, - }: { - requestBody?: FolderCreateModel, - }): CancelablePromise { +requestBody, +}: { +requestBody?: FolderCreateModel, +}): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/umbraco/management/api/v1/data-type/folder', @@ -224,10 +224,10 @@ export class DataTypeResource { * @throws ApiError */ public static deleteDataTypeFolderByKey({ - key, - }: { - key: string, - }): CancelablePromise { +key, +}: { +key: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/umbraco/management/api/v1/data-type/folder/{key}', @@ -245,12 +245,12 @@ export class DataTypeResource { * @throws ApiError */ public static putDataTypeFolderByKey({ - key, - requestBody, - }: { - key: string, - requestBody?: FolderUpdateModel, - }): CancelablePromise { +key, +requestBody, +}: { +key: string, +requestBody?: FolderUpdateModel, +}): CancelablePromise { return __request(OpenAPI, { method: 'PUT', url: '/umbraco/management/api/v1/data-type/folder/{key}', diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/DictionaryResource.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/DictionaryResource.ts index fd8242eca7..62380cf9f4 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/DictionaryResource.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/DictionaryResource.ts @@ -44,10 +44,10 @@ export class DictionaryResource { * @throws ApiError */ public static postDictionary({ - requestBody, - }: { - requestBody?: DictionaryItemCreateModel, - }): CancelablePromise { +requestBody, +}: { +requestBody?: DictionaryItemCreateModel, +}): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/umbraco/management/api/v1/dictionary', @@ -87,10 +87,10 @@ export class DictionaryResource { * @throws ApiError */ public static deleteDictionaryByKey({ - key, - }: { - key: string, - }): CancelablePromise { +key, +}: { +key: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/umbraco/management/api/v1/dictionary/{key}', @@ -109,12 +109,12 @@ export class DictionaryResource { * @throws ApiError */ public static putDictionaryByKey({ - key, - requestBody, - }: { - key: string, - requestBody?: DictionaryItemUpdateModel, - }): CancelablePromise { +key, +requestBody, +}: { +key: string, +requestBody?: DictionaryItemUpdateModel, +}): CancelablePromise { return __request(OpenAPI, { method: 'PUT', url: '/umbraco/management/api/v1/dictionary/{key}', diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/LogViewerResource.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/LogViewerResource.ts index b0898ff328..520dfab7fe 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/LogViewerResource.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/LogViewerResource.ts @@ -41,12 +41,12 @@ export class LogViewerResource { * @throws ApiError */ public static getLogViewerLevelCount({ - startDate, - endDate, - }: { - startDate?: string, - endDate?: string, - }): CancelablePromise { +startDate, +endDate, +}: { +startDate?: string, +endDate?: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/umbraco/management/api/v1/log-viewer/level-count', @@ -193,10 +193,10 @@ export class LogViewerResource { * @throws ApiError */ public static deleteLogViewerSavedSearchByName({ - name, - }: { - name: string, - }): CancelablePromise { +name, +}: { +name: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/umbraco/management/api/v1/log-viewer/saved-search/{name}', @@ -214,12 +214,12 @@ export class LogViewerResource { * @throws ApiError */ public static getLogViewerValidateLogsSize({ - startDate, - endDate, - }: { - startDate?: string, - endDate?: string, - }): CancelablePromise { +startDate, +endDate, +}: { +startDate?: string, +endDate?: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/umbraco/management/api/v1/log-viewer/validate-logs-size', diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/RedirectManagementResource.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/RedirectManagementResource.ts index aa15ce40ac..fbbbab0c83 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/RedirectManagementResource.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/RedirectManagementResource.ts @@ -69,10 +69,10 @@ export class RedirectManagementResource { * @throws ApiError */ public static deleteRedirectManagementByKey({ - key, - }: { - key: string, - }): CancelablePromise { +key, +}: { +key: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/umbraco/management/api/v1/redirect-management/{key}', diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/TemplateResource.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/TemplateResource.ts index 1767d41ef9..b388a78682 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/TemplateResource.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/TemplateResource.ts @@ -27,10 +27,10 @@ export class TemplateResource { * @throws ApiError */ public static postTemplate({ - requestBody, - }: { - requestBody?: TemplateCreateModel, - }): CancelablePromise { +requestBody, +}: { +requestBody?: TemplateCreateModel, +}): CancelablePromise { return __request(OpenAPI, { method: 'POST', url: '/umbraco/management/api/v1/template', @@ -69,10 +69,10 @@ export class TemplateResource { * @throws ApiError */ public static deleteTemplateByKey({ - key, - }: { - key: string, - }): CancelablePromise { +key, +}: { +key: string, +}): CancelablePromise { return __request(OpenAPI, { method: 'DELETE', url: '/umbraco/management/api/v1/template/{key}', @@ -91,12 +91,12 @@ export class TemplateResource { * @throws ApiError */ public static putTemplateByKey({ - key, - requestBody, - }: { - key: string, - requestBody?: TemplateUpdateModel, - }): CancelablePromise { +key, +requestBody, +}: { +key: string, +requestBody?: TemplateUpdateModel, +}): CancelablePromise { return __request(OpenAPI, { method: 'PUT', url: '/umbraco/management/api/v1/template/{key}', diff --git a/src/Umbraco.Web.UI.Client/libs/models/index.ts b/src/Umbraco.Web.UI.Client/libs/models/index.ts index 7dfcad5f6a..4f66e3349c 100644 --- a/src/Umbraco.Web.UI.Client/libs/models/index.ts +++ b/src/Umbraco.Web.UI.Client/libs/models/index.ts @@ -1,5 +1,6 @@ import { ContentTreeItemModel, + DictionaryItemTranslationModel, EntityTreeItemModel, FolderTreeItemModel, ProblemDetailsModel, @@ -127,6 +128,7 @@ export interface MemberDetails extends EntityTreeItemModel { // Dictionary export interface DictionaryDetails extends EntityTreeItemModel { key: string; // TODO: Remove this when the backend is fixed + translations: DictionaryItemTranslationModel[]; } // Document Blueprint diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts index 829b1ea34e..fe7030042d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts @@ -29,8 +29,8 @@ import { UmbMemberGroupDetailStore } from './members/member-groups/member-group. import { UmbMemberGroupTreeStore } from './members/member-groups/repository/member-group.tree.store'; import { UmbMemberDetailStore } from './members/members/member.detail.store'; import { UmbMemberTreeStore } from './members/members/repository/member.tree.store'; -import { UmbDictionaryDetailStore } from './translation/dictionary/dictionary.detail.store'; -import { UmbDictionaryTreeStore } from './translation/dictionary/dictionary.tree.store'; +import { UmbDictionaryDetailStore } from './translation/dictionary/repository/dictionary.detail.store'; +import { UmbDictionaryTreeStore } from './translation/dictionary/repository/dictionary.tree.store'; import { UmbDocumentBlueprintDetailStore } from './documents/document-blueprints/document-blueprint.detail.store'; import { UmbDocumentBlueprintTreeStore } from './documents/document-blueprints/document-blueprint.tree.store'; import { UmbDataTypeStore } from './settings/data-types/repository/data-type.store'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts index 31fabab8cb..c9602d9759 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts @@ -29,7 +29,6 @@ import './input-media-picker/input-media-picker.element'; import './input-document-picker/input-document-picker.element'; import './empty-state/empty-state.element'; - import './color-picker/color-picker.element'; import './debug/debug.element'; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-dashboards/section-dashboards.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-dashboards/section-dashboards.element.ts index c3f2d1f68d..a3d8f6a3d2 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-dashboards/section-dashboards.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-dashboards/section-dashboards.element.ts @@ -37,6 +37,19 @@ export class UmbSectionDashboardsElement extends UmbLitElement { display: block; padding: var(--uui-size-5); } + + #header { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + min-height: 60px; + box-sizing: border-box; + margin:0; + padding:0 var(--uui-size-5); + background-color:var(--uui-color-surface); + border-bottom:1px solid var(--uui-color-border); + } `, ]; @@ -140,6 +153,8 @@ export class UmbSectionDashboardsElement extends UmbLitElement { )} ` + : this._dashboards?.length === 1 + ? html`` : nothing} `; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts index 994198f6b7..ca2411549a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/section/section-sidebar/section-sidebar.element.ts @@ -21,6 +21,7 @@ export class UmbSectionSidebarElement extends UmbLitElement { font-weight: 500; display: flex; flex-direction: column; + z-index:10; } h3 { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/table/table.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/table/table.element.ts index baf6ffd916..67a2bfa9b1 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/table/table.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/table/table.element.ts @@ -221,8 +221,8 @@ export class UmbTableElement extends LitElement { private _renderHeaderCell(column: UmbTableColumn) { return html` - - ${column.allowSorting ? html`${this._renderSortingUI(column)}` : nothing} + + ${column.allowSorting ? html`${this._renderSortingUI(column)}` : column.name} `; } @@ -284,9 +284,8 @@ export class UmbTableElement extends LitElement { } private _renderRowCell(column: UmbTableColumn, item: UmbTableItem) { - return html`${this._renderCellContent(column, item)} + return html`${this._renderCellContent(column, item)} `; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace-property/workspace-property.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace-property/workspace-property.element.ts index 877c4874bf..0b3a44e142 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace-property/workspace-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace-property/workspace-property.element.ts @@ -27,6 +27,10 @@ export class UmbWorkspacePropertyElement extends UmbLitElement { display: block; } + :host(:last-child) umb-workspace-property-layout { + border-bottom:0; + } + p { color: var(--uui-color-text-alt); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dashboards/dictionary/dashboard-translation-dictionary.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dashboards/dictionary/dashboard-translation-dictionary.element.ts new file mode 100644 index 0000000000..f9f80ed69a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dashboards/dictionary/dashboard-translation-dictionary.element.ts @@ -0,0 +1,203 @@ +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { css, html } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; +import { when } from 'lit-html/directives/when.js'; +import { UmbTableConfig, UmbTableColumn, UmbTableItem } from '../../../../backoffice/shared/components/table'; +import { UmbDictionaryRepository } from '../../dictionary/repository/dictionary.repository'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { DictionaryOverviewModel, LanguageModel } from '@umbraco-cms/backend-api'; +import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal'; +import { UmbContextConsumerController } from '@umbraco-cms/context-api'; +import { UmbCreateDictionaryModalResultData } from '../../dictionary/entity-actions/create/create-dictionary-modal-layout.element'; + +@customElement('umb-dashboard-translation-dictionary') +export class UmbDashboardTranslationDictionaryElement extends UmbLitElement { + static styles = [ + UUITextStyles, + css` + :host { + display: flex; + flex-direction: column; + height: 100%; + } + + #dictionary-top-bar { + margin-bottom: var(--uui-size-space-5); + display: flex; + justify-content: space-between; + } + + umb-table { + display: inline; + padding: 0; + } + + umb-empty-state { + margin: auto; + font-size: var(--uui-size-6); + } + `, + ]; + + @state() + private _tableConfig: UmbTableConfig = { + allowSelection: false, + }; + + @state() + private _tableItemsFiltered: Array = []; + + #dictionaryItems: DictionaryOverviewModel[] = []; + + #repo!: UmbDictionaryRepository; + + #modalService!: UmbModalService; + + #tableItems: Array = []; + + #tableColumns: Array = []; + + #languages: Array = []; + + constructor() { + super(); + + new UmbContextConsumerController(this, UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => { + this.#modalService = instance; + }); + } + + async connectedCallback() { + super.connectedCallback(); + + this.#repo = new UmbDictionaryRepository(this); + this.#languages = await this.#repo.getLanguages(); + await this.#getDictionaryItems(); + } + + async #getDictionaryItems() { + if (!this.#repo) return; + + const { data } = await this.#repo.list(0, 1000); + this.#dictionaryItems = data?.items ?? []; + this.#setTableColumns(); + this.#setTableItems(); + } + + /** + * We don't know how many translation items exist for each dictionary until the data arrives + * so can not generate the columns in advance. + * @returns + */ + #setTableColumns() { + this.#tableColumns = [ + { + name: 'Name', + alias: 'name', + }, + ]; + + this.#languages.forEach((l) => { + if (!l.name) return; + + this.#tableColumns.push({ + name: l.name ?? '', + alias: l.isoCode ?? '', + }); + }); + } + + #setTableItems() { + this.#tableItems = this.#dictionaryItems.map((dictionary) => { + // key is name to allow filtering on the displayed value + const tableItem: UmbTableItem = { + key: dictionary.name ?? '', + icon: 'umb:book-alt', + data: [ + { + columnAlias: 'name', + value: html` + ${dictionary.name} `, + }, + ], + }; + + this.#languages.forEach((l) => { + if (!l.isoCode) return; + + tableItem.data.push({ + columnAlias: l.isoCode, + value: dictionary.translatedIsoCodes?.includes(l.isoCode) + ? html`` + : html``, + }); + }); + + return tableItem; + }); + + this._tableItemsFiltered = this.#tableItems; + } + + #filter(e: { target: HTMLInputElement }) { + this._tableItemsFiltered = e.target.value + ? this.#tableItems.filter((t) => t.key.includes(e.target.value)) + : this.#tableItems; + } + + async #create() { + // TODO: what to do if modal service is not available? + if (!this.#modalService) return; + + const modalHandler = this.#modalService?.open('umb-create-dictionary-modal-layout', { + type: 'sidebar', + data: { unique: null }, + }); + + // TODO: get type from modal result + const { name }: UmbCreateDictionaryModalResultData = await modalHandler.onClose(); + if (!name) return; + + const result = await this.#repo?.createDetail({ name, parentKey: null, translations: [], key: '' }); + + // TODO => get location header to route to new item + console.log(result); + } + + render() { + return html`
+ Create dictionary item + +
+ +
+
+
+ ${when( + this._tableItemsFiltered.length, + () => html` `, + () => html`There were no dictionary items found.` + )}`; + } +} + +export default UmbDashboardTranslationDictionaryElement; +declare global { + interface HTMLElementTagNameMap { + 'umb-dashboard-translation-dictionary': UmbDashboardTranslationDictionaryElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/dictionary.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/dictionary.detail.store.ts deleted file mode 100644 index 86275c410d..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/dictionary.detail.store.ts +++ /dev/null @@ -1,100 +0,0 @@ -import type { DictionaryDetails } from '@umbraco-cms/models'; -import { UmbContextToken } from '@umbraco-cms/context-api'; -import { ArrayState } from '@umbraco-cms/observable-api'; -import { UmbEntityDetailStore, UmbStoreBase } from '@umbraco-cms/store'; -import type { UmbControllerHostInterface } from '@umbraco-cms/controller'; -import type { EntityTreeItemModel } from '@umbraco-cms/backend-api'; - -export const UMB_DICTIONARY_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken( - 'UmbDictionaryDetailStore' -); - -/** - * @export - * @class UmbDictionaryDetailStore - * @extends {UmbStoreBase} - * @description - Details Data Store for Data Types - */ -// TODO: use the right type for dictionary: -export class UmbDictionaryDetailStore extends UmbStoreBase implements UmbEntityDetailStore { - // TODO: use the right type: - #data = new ArrayState([], (x) => x.key); - - constructor(host: UmbControllerHostInterface) { - super(host, UMB_DICTIONARY_DETAIL_STORE_CONTEXT_TOKEN.toString()); - } - - getScaffold(entityType: string, parentKey: string | null) { - return {} as EntityTreeItemModel; - } - - /** - * @description - Request a Data Type by key. The Data Type is added to the store and is returned as an Observable. - * @param {string} key - * @return {*} {(Observable)} - * @memberof UmbDictionaryDetailStore - */ - getByKey(key: string) { - // TODO: use backend cli when available. - fetch(`/umbraco/management/api/v1/dictionary/details/${key}`) - .then((res) => res.json()) - .then((data) => { - this.#data.append(data); - }); - - return this.#data.getObservablePart((documents) => documents.find((document) => document.key === key)); - } - - // TODO: make sure UI somehow can follow the status of this action. - /** - * @description - Save a Dictionary. - * @param {Array} Dictionaries - * @memberof UmbDictionaryDetailStore - * @return {*} {Promise} - */ - save(data: DictionaryDetails[]) { - // fetch from server and update store - // TODO: use Fetcher API. - let body: string; - - try { - body = JSON.stringify(data); - } catch (error) { - console.error(error); - return Promise.reject(); - } - - // TODO: use backend cli when available. - return fetch('/umbraco/management/api/v1/dictionary/save', { - method: 'POST', - body: body, - headers: { - 'Content-Type': 'application/json', - }, - }) - .then((res) => res.json()) - .then((data: Array) => { - this.#data.append(data); - }); - } - - // TODO: How can we avoid having this in both stores? - /** - * @description - Delete a Data Type. - * @param {string[]} keys - * @memberof UmbDictionaryDetailStore - * @return {*} {Promise} - */ - async delete(keys: string[]) { - // TODO: use backend cli when available. - await fetch('/umbraco/backoffice/dictionary/delete', { - method: 'POST', - body: JSON.stringify(keys), - headers: { - 'Content-Type': 'application/json', - }, - }); - - this.#data.remove(keys); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/dictionary.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/dictionary.tree.store.ts deleted file mode 100644 index a873a19241..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/dictionary.tree.store.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { DictionaryResource, DocumentTreeItemModel } from '@umbraco-cms/backend-api'; -import { tryExecuteAndNotify } from '@umbraco-cms/resources'; -import { UmbContextToken } from '@umbraco-cms/context-api'; -import { ArrayState } from '@umbraco-cms/observable-api'; -import { UmbStoreBase } from '@umbraco-cms/store'; -import { UmbControllerHostInterface } from '@umbraco-cms/controller'; - -export const UMB_DICTIONARY_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken( - 'UmbDictionaryTreeStore' -); - -/** - * @export - * @class UmbDictionaryTreeStore - * @extends {UmbStoreBase} - * @description - Tree Data Store for Data Types - */ -export class UmbDictionaryTreeStore extends UmbStoreBase { - #data = new ArrayState([], (x) => x.key); - - constructor(host: UmbControllerHostInterface) { - super(host, UMB_DICTIONARY_TREE_STORE_CONTEXT_TOKEN.toString()); - } - - // TODO: How can we avoid having this in both stores? - /** - * @description - Delete a Data Type. - * @param {string[]} keys - * @memberof UmbDictionarysStore - * @return {*} {Promise} - */ - async delete(keys: string[]) { - // TODO: use backend cli when available. - await fetch('/umbraco/backoffice/data-type/delete', { - method: 'POST', - body: JSON.stringify(keys), - headers: { - 'Content-Type': 'application/json', - }, - }); - - this.#data.remove(keys); - } - - getTreeRoot() { - tryExecuteAndNotify(this._host, DictionaryResource.getTreeDictionaryRoot({})).then(({ data }) => { - if (data) { - // TODO: how do we handle if an item has been removed during this session(like in another tab or by another user)? - this.#data.append(data.items); - } - }); - - // TODO: how do we handle trashed items? - // TODO: remove ignore when we know how to handle trashed items. - return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === null && !item.isTrashed)); - } - - getTreeItemChildren(key: string) { - tryExecuteAndNotify( - this._host, - DictionaryResource.getTreeDictionaryChildren({ - parentKey: key, - }) - ).then(({ data }) => { - if (data) { - // TODO: how do we handle if an item has been removed during this session(like in another tab or by another user)? - this.#data.append(data.items); - } - }); - - // TODO: how do we handle trashed items? - // TODO: remove ignore when we know how to handle trashed items. - return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === key && !item.isTrashed)); - } - - getTreeItems(keys: Array) { - if (keys?.length > 0) { - tryExecuteAndNotify( - this._host, - DictionaryResource.getTreeDictionaryItem({ - key: keys, - }) - ).then(({ data }) => { - if (data) { - // TODO: how do we handle if an item has been removed during this session(like in another tab or by another user)? - this.#data.append(data); - } - }); - } - - return this.#data.getObservablePart((items) => items.filter((item) => keys.includes(item.key ?? ''))); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/create/create-dictionary-modal-layout.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/create/create-dictionary-modal-layout.element.ts new file mode 100644 index 0000000000..0ad2d98afd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/create/create-dictionary-modal-layout.element.ts @@ -0,0 +1,84 @@ +import { html } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, query } from 'lit/decorators.js'; +import { Observable } from 'rxjs'; +import { when } from 'lit-html/directives/when.js'; +import { UmbModalLayoutElement } from '@umbraco-cms/modal'; + +export interface UmbCreateDictionaryModalData { + unique: string | null; + parentName: Observable +} + +export interface UmbCreateDictionaryModalResultData { + name?: string; +} + +@customElement('umb-create-dictionary-modal-layout') +export class UmbCreateDictionaryModalLayoutElement extends UmbModalLayoutElement { + static styles = [UUITextStyles]; + + @query('#form') + private _form!: HTMLFormElement; + + #parentName?: string; + + connectedCallback() { + super.connectedCallback(); + + if (this.data?.parentName) { + this.observe(this.data.parentName, (value) => this.#parentName = value); + } + } + + #handleCancel() { + this.modalHandler?.close({}); + } + + #submitForm() { + this._form?.requestSubmit(); + } + + async #handleSubmit(e: SubmitEvent) { + e.preventDefault(); + + const form = e.target as HTMLFormElement; + if (!form || !form.checkValidity()) return; + + const formData = new FormData(form); + + this.modalHandler?.close({ + name: formData.get('name') as string, + }); + } + + render() { + return html` + ${when(this.#parentName, () => html`

Create a dictionary item under ${this.#parentName}

`)} + +
+ + Name +
+ +
+
+
+
+ + +
`; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-create-dictionary-modal-layout': UmbCreateDictionaryModalLayoutElement; + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/create/create.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/create/create.action.ts new file mode 100644 index 0000000000..53b6d4117f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/create/create.action.ts @@ -0,0 +1,55 @@ +import { UUITextStyles } from '@umbraco-ui/uui-css'; +import { UmbEntityActionBase } from '../../../../shared/entity-actions'; +import { UmbDictionaryRepository } from '../../repository/dictionary.repository'; +import { + UmbSectionSidebarContext, + UMB_SECTION_SIDEBAR_CONTEXT_TOKEN, +} from '../../../../../backoffice/shared/components/section/section-sidebar/section-sidebar.context'; +import type { UmbCreateDictionaryModalResultData } from './create-dictionary-modal-layout.element'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { UmbContextConsumerController } from '@umbraco-cms/context-api'; +import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal'; + +// TODO: temp import +import './create-dictionary-modal-layout.element'; + +export default class UmbCreateDictionaryEntityAction extends UmbEntityActionBase { + static styles = [UUITextStyles]; + + #modalService?: UmbModalService; + + #sectionSidebarContext!: UmbSectionSidebarContext; + + constructor(host: UmbControllerHostInterface, repositoryAlias: string, unique: string) { + super(host, repositoryAlias, unique); + + new UmbContextConsumerController(this.host, UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => { + this.#modalService = instance; + }); + + new UmbContextConsumerController(this.host, UMB_SECTION_SIDEBAR_CONTEXT_TOKEN, (instance) => { + this.#sectionSidebarContext = instance; + }); + } + + async execute() { + // TODO: what to do if modal service is not available? + if (!this.#modalService) return; + + // TODO: how can we get the current entity detail in the modal? Passing the observable + // feels a bit hacky. Works, but hacky. + const modalHandler = this.#modalService?.open('umb-create-dictionary-modal-layout', { + type: 'sidebar', + data: { unique: this.unique, parentName: this.#sectionSidebarContext.headline }, + }); + + // TODO: get type from modal result + const { name }: UmbCreateDictionaryModalResultData = await modalHandler.onClose(); + if (!name) return; + + const result = await this.repository?.createDetail({ name, parentKey: this.unique, translations: [], key: ''}); + + // TODO => get location header to route to new item + console.log(result); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/export/export-dictionary-modal-layout.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/export/export-dictionary-modal-layout.element.ts new file mode 100644 index 0000000000..690cea3993 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/export/export-dictionary-modal-layout.element.ts @@ -0,0 +1,60 @@ +import { html } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, query } from 'lit/decorators.js'; +import { UmbModalLayoutElement } from '@umbraco-cms/modal'; + +export interface UmbExportDictionaryModalData { + unique: string | null; +} + +export interface UmbExportDictionaryModalResultData { + includeChildren?: boolean; +} + +@customElement('umb-export-dictionary-modal-layout') +export class UmbExportDictionaryModalLayoutElement extends UmbModalLayoutElement { + static styles = [UUITextStyles]; + + @query('#form') + private _form!: HTMLFormElement; + + #handleClose() { + this.modalHandler?.close({}); + } + + #submitForm() { + this._form?.requestSubmit(); + } + + async #handleSubmit(e: SubmitEvent) { + e.preventDefault(); + + const form = e.target as HTMLFormElement; + if (!form) return; + + const formData = new FormData(form); + + this.modalHandler?.close({ includeChildren: (formData.get('includeDescendants') as string) === 'on' }); + } + + render() { + return html` + +
+ + Include descendants + + +
+
+ + +
`; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-export-dictionary-modal-layout': UmbExportDictionaryModalLayoutElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/export/export.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/export/export.action.ts new file mode 100644 index 0000000000..d13e557ded --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/export/export.action.ts @@ -0,0 +1,42 @@ +import { UUITextStyles } from '@umbraco-ui/uui-css'; +import { UmbEntityActionBase } from '../../../../shared/entity-actions'; +import { UmbDictionaryRepository } from '../../repository/dictionary.repository'; +import type { UmbExportDictionaryModalResultData } from './export-dictionary-modal-layout.element'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { UmbContextConsumerController } from '@umbraco-cms/context-api'; +import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal'; + +import './export-dictionary-modal-layout.element'; + +export default class UmbExportDictionaryEntityAction extends UmbEntityActionBase { + static styles = [UUITextStyles]; + + #modalService?: UmbModalService; + + constructor(host: UmbControllerHostInterface, repositoryAlias: string, unique: string) { + super(host, repositoryAlias, unique); + + new UmbContextConsumerController(this.host, UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => { + this.#modalService = instance; + }); + } + + async execute() { + // TODO: what to do if modal service is not available? + if (!this.#modalService) return; + + const modalHandler = this.#modalService?.open('umb-export-dictionary-modal-layout', { + type: 'sidebar', + data: { unique: this.unique }, + }); + + // TODO: get type from modal result + const { includeChildren }: UmbExportDictionaryModalResultData = await modalHandler.onClose(); + if (includeChildren === undefined) return; + + const result = await this.repository?.export(this.unique, includeChildren); + + // TODO => get location header to route to new item + console.log(result); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/import/import-dictionary-modal-layout.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/import/import-dictionary-modal-layout.element.ts new file mode 100644 index 0000000000..a2fe92a240 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/import/import-dictionary-modal-layout.element.ts @@ -0,0 +1,163 @@ +import { css, html } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, query, state } from 'lit/decorators.js'; +import { when } from 'lit-html/directives/when.js'; +import { repeat } from 'lit/directives/repeat.js'; +import { UmbTreeElement } from '../../../../shared/components/tree/tree.element'; +import { UmbDictionaryRepository } from '../../repository/dictionary.repository'; +import { DictionaryUploadModel } from '@umbraco-cms/backend-api'; +import { UmbModalLayoutElement } from '@umbraco-cms/modal'; + +export interface UmbImportDictionaryModalData { + unique: string | null; +} + +export interface UmbImportDictionaryModalResultData { + fileName?: string; + parentKey?: string; +} + +@customElement('umb-import-dictionary-modal-layout') +export class UmbImportDictionaryModalLayoutElement extends UmbModalLayoutElement { + static styles = [ + UUITextStyles, + css` + uui-input { + width: 100%; + } + `, + ]; + + @query('#form') + private _form!: HTMLFormElement; + + @state() + private _uploadedDictionary?: DictionaryUploadModel; + + @state() + private _showUploadView = true; + + @state() + private _showImportView = false; + + @state() + private _showErrorView = false; + + @state() + private _selection: Array = []; + + #detailRepo = new UmbDictionaryRepository(this); + + async #importDictionary() { + if (!this._uploadedDictionary?.fileName) return; + + this.modalHandler?.close({ + fileName: this._uploadedDictionary.fileName, + parentKey: this._selection[0], + }); + } + + #handleClose() { + this.modalHandler?.close({}); + } + + #submitForm() { + this._form?.requestSubmit(); + } + + async #handleSubmit(e: SubmitEvent) { + e.preventDefault(); + + if (!this._form.checkValidity()) return; + + const formData = new FormData(this._form); + const { data } = await this.#detailRepo.upload(formData); + + this._uploadedDictionary = data; + + if (!this._uploadedDictionary) { + this._showErrorView = true; + this._showImportView = false; + return; + } + + this._showErrorView = false; + this._showUploadView = false; + this._showImportView = true; + } + + #handleSelectionChange(e: CustomEvent) { + e.stopPropagation(); + const element = e.target as UmbTreeElement; + this._selection = element.selection; + } + + #renderUploadView() { + return html`

+ To import a dictionary item, find the ".udt" file on your computer by clicking the "Import" button (you'll be + asked for confirmation on the next screen) +

+ +
+ + File +
+ +
+
+
+
+ + `; + } + + /// TODO => Tree view needs isolation and single-select option + #renderImportView() { + if (!this._uploadedDictionary?.dictionaryItems) return; + + return html` + Dictionary items +
    + ${repeat( + this._uploadedDictionary.dictionaryItems, + (item) => item.name, + (item) => html`
  • ${item.name}
  • ` + )} +
+
+ Choose where to import dictionary items (optional) + + + + + `; + } + + // TODO => Determine what to display when dictionary import/upload fails + #renderErrorView() { + return html`Something went wrong`; + } + + render() { + return html` + ${when(this._showUploadView, () => this.#renderUploadView())} + ${when(this._showImportView, () => this.#renderImportView())} + ${when(this._showErrorView, () => this.#renderErrorView())} + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-import-dictionary-modal-layout': UmbImportDictionaryModalLayoutElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/import/import.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/import/import.action.ts new file mode 100644 index 0000000000..b091cc0e36 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/import/import.action.ts @@ -0,0 +1,42 @@ +import { UUITextStyles } from '@umbraco-ui/uui-css'; +import { UmbEntityActionBase } from '../../../../shared/entity-actions'; +import { UmbDictionaryRepository } from '../../repository/dictionary.repository'; +import type { UmbImportDictionaryModalResultData } from './import-dictionary-modal-layout.element'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { UmbContextConsumerController } from '@umbraco-cms/context-api'; +import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal'; + +import './import-dictionary-modal-layout.element'; + +export default class UmbImportDictionaryEntityAction extends UmbEntityActionBase { + static styles = [UUITextStyles]; + + #modalService?: UmbModalService; + + constructor(host: UmbControllerHostInterface, repositoryAlias: string, unique: string) { + super(host, repositoryAlias, unique); + + new UmbContextConsumerController(this.host, UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => { + this.#modalService = instance; + }); + } + + async execute() { + // TODO: what to do if modal service is not available? + if (!this.#modalService) return; + + const modalHandler = this.#modalService?.open('umb-import-dictionary-modal-layout', { + type: 'sidebar', + data: { unique: this.unique }, + }); + + // TODO: get type from modal result + const { fileName, parentKey }: UmbImportDictionaryModalResultData = await modalHandler.onClose(); + if (!fileName) return; + + const result = await this.repository?.import(fileName, parentKey); + + // TODO => get location header to route to new item + console.log(result); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/manifests.ts new file mode 100644 index 0000000000..3449a3787a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/manifests.ts @@ -0,0 +1,93 @@ +import { UmbDeleteEntityAction } from '../../../../backoffice/shared/entity-actions/delete/delete.action'; +import { UmbMoveEntityAction } from '../../../../backoffice/shared/entity-actions/move/move.action'; +import UmbReloadDictionaryEntityAction from './reload.action'; +import UmbImportDictionaryEntityAction from './import/import.action'; +import UmbExportDictionaryEntityAction from './export/export.action'; +import UmbCreateDictionaryEntityAction from './create/create.action'; +import type { ManifestEntityAction } from '@umbraco-cms/models'; + +const entityType = 'dictionary-item'; +const repositoryAlias = 'Umb.Repository.Dictionary'; + +const entityActions: Array = [ + { + type: 'entityAction', + alias: 'Umb.EntityAction.Dictionary.Create', + name: 'Create Dictionary Entity Action', + weight: 600, + meta: { + entityType, + icon: 'umb:add', + label: 'Create', + repositoryAlias, + api: UmbCreateDictionaryEntityAction, + }, + }, + { + type: 'entityAction', + alias: 'Umb.EntityAction.Dictionary.Move', + name: 'Move Dictionary Entity Action', + weight: 500, + meta: { + entityType, + icon: 'umb:enter', + label: 'Move', + repositoryAlias, + api: UmbMoveEntityAction, + }, + }, + { + type: 'entityAction', + alias: 'Umb.EntityAction.Dictionary.Export', + name: 'Export Dictionary Entity Action', + weight: 400, + meta: { + entityType, + icon: 'umb:download-alt', + label: 'Export', + repositoryAlias, + api: UmbExportDictionaryEntityAction, + }, + }, + { + type: 'entityAction', + alias: 'Umb.EntityAction.Dictionary.Import', + name: 'Import Dictionary Entity Action', + weight: 300, + meta: { + entityType, + icon: 'umb:page-up', + label: 'Import', + repositoryAlias, + api: UmbImportDictionaryEntityAction, + }, + }, + { + type: 'entityAction', + alias: 'Umb.EntityAction.Dictionary.Reload', + name: 'Reload Dictionary Entity Action', + weight: 200, + meta: { + entityType, + icon: 'umb:refresh', + label: 'Reload', + repositoryAlias, + api: UmbReloadDictionaryEntityAction, + }, + }, + { + type: 'entityAction', + alias: 'Umb.EntityAction.Dictionary.Delete', + name: 'Delete Dictionary Entity Action', + weight: 100, + meta: { + entityType, + icon: 'umb:trash', + label: 'Delete', + repositoryAlias, + api: UmbDeleteEntityAction, + }, + }, +]; + +export const manifests = [...entityActions]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/reload.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/reload.action.ts new file mode 100644 index 0000000000..19f607129d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/entity-actions/reload.action.ts @@ -0,0 +1,16 @@ +import { UUITextStyles } from '@umbraco-ui/uui-css'; +import { UmbEntityActionBase } from '../../../shared/entity-actions'; +import { UmbDictionaryRepository } from '../repository/dictionary.repository'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; + +export default class UmbReloadDictionaryEntityAction extends UmbEntityActionBase { + static styles = [UUITextStyles]; + + constructor(host: UmbControllerHostInterface, repositoryAlias: string, unique: string) { + super(host, repositoryAlias, unique); + } + + async execute() { + alert('refresh') + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/manifests.ts index a4edc8b4f1..6c4d56a671 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/manifests.ts @@ -1,5 +1,13 @@ import { manifests as sidebarMenuItemManifests } from './sidebar-menu-item/manifests'; import { manifests as treeManifests } from './tree/manifests'; +import { manifests as repositoryManifests } from './repository/manifests'; import { manifests as workspaceManifests } from './workspace/manifests'; +import { manifests as entityActionManifests } from './entity-actions/manifests'; -export const manifests = [...sidebarMenuItemManifests, ...treeManifests, ...workspaceManifests]; +export const manifests = [ + ...sidebarMenuItemManifests, + ...treeManifests, + ...repositoryManifests, + ...workspaceManifests, + ...entityActionManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.detail.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.detail.store.ts new file mode 100644 index 0000000000..4b1ab81c72 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.detail.store.ts @@ -0,0 +1,33 @@ +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { UmbStoreBase } from '@umbraco-cms/store'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { ArrayState } from '@umbraco-cms/observable-api'; +import type { DictionaryDetails } from '@umbraco-cms/models'; + +/** + * @export + * @class UmbDictionaryDetailStore + * @extends {UmbStoreBase} + * @description - Details Data Store for Data Types + */ +export class UmbDictionaryDetailStore + extends UmbStoreBase +{ + #data = new ArrayState([], (x) => x.key); + + constructor(host: UmbControllerHostInterface) { + super(host, UmbDictionaryDetailStore.name); + } + + append(dictionary: DictionaryDetails) { + this.#data.append([dictionary]); + } + + remove(uniques: string[]) { + this.#data.remove(uniques); + } +} + +export const UMB_DICTIONARY_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken( + UmbDictionaryDetailStore.name +); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts new file mode 100644 index 0000000000..6315120503 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts @@ -0,0 +1,247 @@ +import { DictionaryTreeServerDataSource } from './sources/dictionary.tree.server.data'; +import { UmbDictionaryTreeStore, UMB_DICTIONARY_TREE_STORE_CONTEXT_TOKEN } from './dictionary.tree.store'; +import { UmbDictionaryDetailStore, UMB_DICTIONARY_DETAIL_STORE_CONTEXT_TOKEN } from './dictionary.detail.store'; +import { UmbDictionaryDetailServerDataSource } from './sources/dictionary.detail.server.data'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { UmbContextConsumerController } from '@umbraco-cms/context-api'; +import { RepositoryTreeDataSource, UmbTreeRepository } from '@umbraco-cms/repository'; +import { ProblemDetailsModel } from '@umbraco-cms/backend-api'; +import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification'; +import type { DictionaryDetails } from '@umbraco-cms/models'; + +export class UmbDictionaryRepository implements UmbTreeRepository { + #init!: Promise; + + #host: UmbControllerHostInterface; + + #treeSource: RepositoryTreeDataSource; + #treeStore?: UmbDictionaryTreeStore; + + #detailSource: UmbDictionaryDetailServerDataSource; + #detailStore?: UmbDictionaryDetailStore; + + #notificationService?: UmbNotificationService; + + constructor(host: UmbControllerHostInterface) { + this.#host = host; + + // TODO: figure out how spin up get the correct data source + this.#treeSource = new DictionaryTreeServerDataSource(this.#host); + this.#detailSource = new UmbDictionaryDetailServerDataSource(this.#host); + + this.#init = Promise.all([ + new UmbContextConsumerController(this.#host, UMB_DICTIONARY_DETAIL_STORE_CONTEXT_TOKEN, (instance) => { + this.#detailStore = instance; + }), + + new UmbContextConsumerController(this.#host, UMB_DICTIONARY_TREE_STORE_CONTEXT_TOKEN, (instance) => { + this.#treeStore = instance; + }), + + new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (instance) => { + this.#notificationService = instance; + }), + ]); + } + + async requestRootTreeItems() { + await this.#init; + + const { data, error } = await this.#treeSource.getRootItems(); + + if (data) { + this.#treeStore?.appendItems(data.items); + } + + return { data, error, asObservable: () => this.#treeStore!.rootItems }; + } + + async requestTreeItemsOf(parentKey: string | null) { + await this.#init; + + if (!parentKey) { + const error: ProblemDetailsModel = { title: 'Parent key is missing' }; + return { data: undefined, error }; + } + + const { data, error } = await this.#treeSource.getChildrenOf(parentKey); + + if (data) { + this.#treeStore?.appendItems(data.items); + } + + return { data, error, asObservable: () => this.#treeStore!.childrenOf(parentKey) }; + } + + async requestTreeItems(keys: Array) { + await this.#init; + + if (!keys) { + const error: ProblemDetailsModel = { title: 'Keys are missing' }; + return { data: undefined, error }; + } + + const { data, error } = await this.#treeSource.getItems(keys); + + return { data, error, asObservable: () => this.#treeStore!.items(keys) }; + } + + async rootTreeItems() { + await this.#init; + return this.#treeStore!.rootItems; + } + + async treeItemsOf(parentKey: string | null) { + await this.#init; + return this.#treeStore!.childrenOf(parentKey); + } + + async treeItems(keys: Array) { + await this.#init; + return this.#treeStore!.items(keys); + } + + // DETAILS + + async createDetailsScaffold(parentKey: string | null) { + await this.#init; + + if (!parentKey) { + const error: ProblemDetailsModel = { title: 'Parent key is missing' }; + return { data: undefined, error }; + } + + return this.#detailSource.createScaffold(parentKey); + } + + async requestDetails(key: string) { + await this.#init; + + // TODO: should we show a notification if the key is missing? + // Investigate what is best for Acceptance testing, cause in that perspective a thrown error might be the best choice? + if (!key) { + const error: ProblemDetailsModel = { title: 'Key is missing' }; + return { error }; + } + const { data, error } = await this.#detailSource.get(key); + + if (data) { + this.#detailStore?.append(data); + } + return { data, error }; + } + + async list(skip = 0, take = 1000) { + await this.#init; + return this.#detailSource.list(skip, take); + } + + async delete(key: string) { + await this.#init; + return this.#detailSource.delete(key); + } + + async saveDetail(dictionary: DictionaryDetails) { + await this.#init; + + // TODO: should we show a notification if the dictionary is missing? + // Investigate what is best for Acceptance testing, cause in that perspective a thrown error might be the best choice? + if (!dictionary || !dictionary.key) { + const error: ProblemDetailsModel = { title: 'Dictionary is missing' }; + return { error }; + } + + const { error } = await this.#detailSource.update(dictionary); + + if (!error) { + const notification = { data: { message: `Dictionary '${dictionary.name}' saved` } }; + this.#notificationService?.peek('positive', notification); + } + + // TODO: we currently don't use the detail store for anything. + // Consider to look up the data before fetching from the server + // Consider notify a workspace if a dictionary is updated in the store while someone is editing it. + this.#detailStore?.append(dictionary); + this.#treeStore?.updateItem(dictionary.key, { name: dictionary.name }); + // TODO: would be nice to align the stores on methods/methodNames. + + return { error }; + } + + async createDetail(detail: DictionaryDetails) { + await this.#init; + + if (!detail.name) { + const error: ProblemDetailsModel = { title: 'Name is missing' }; + return { error }; + } + + const { data, error } = await this.#detailSource.insert(detail); + + if (!error) { + const notification = { data: { message: `Dictionary '${detail.name}' created` } }; + this.#notificationService?.peek('positive', notification); + } + + return { data, error }; + } + + async export(key: string, includeChildren = false) { + await this.#init; + + if (!key) { + const error: ProblemDetailsModel = { title: 'Key is missing' }; + return { error }; + } + + return this.#detailSource.export(key, includeChildren); + } + + async import(fileName: string, parentKey?: string) { + await this.#init; + + if (!fileName) { + const error: ProblemDetailsModel = { title: 'File is missing' }; + return { error }; + } + + return this.#detailSource.import(fileName, parentKey); + } + + async upload(formData: FormData) { + await this.#init; + + if (!formData) { + const error: ProblemDetailsModel = { title: 'Form data is missing' }; + return { error }; + } + + return this.#detailSource.upload(formData); + } + + // TODO => temporary only, until languages data source exists, or might be + // ok to keep, as it reduces downstream dependencies + async getLanguages() { + await this.#init; + + const { data } = await this.#detailSource.getLanguages(); + + // default first, then sorted by name + // easier to unshift than conditionally sorting by bool and string + const languages = + data?.items.sort((a, b) => { + a.name = a.name ?? ''; + b.name = b.name ?? ''; + return a.name > b.name ? 1 : b.name > a.name ? -1 : 0; + }) ?? []; + + const defaultIndex = languages.findIndex((x) => x.isDefault); + languages.unshift(...languages.splice(defaultIndex, 1)); + + return languages; + } + + async move() { + alert('move me!'); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.tree.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.tree.store.ts new file mode 100644 index 0000000000..e0df7e7b23 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.tree.store.ts @@ -0,0 +1,25 @@ +import { UmbContextToken } from '@umbraco-cms/context-api'; +import { UmbTreeStoreBase } from '@umbraco-cms/store'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; + +/** + * @export + * @class UmbDictionaryTreeStore + * @extends {UmbTreeStoreBase} + * @description - Tree Data Store for Data Types + */ +export class UmbDictionaryTreeStore extends UmbTreeStoreBase { + + /** + * Creates an instance of UmbDictionaryTreeStore. + * @param {UmbControllerHostInterface} host + * @memberof UmbDictionaryTreeStore + */ + constructor(host: UmbControllerHostInterface) { + super(host, UMB_DICTIONARY_TREE_STORE_CONTEXT_TOKEN.toString()); + } +} + +export const UMB_DICTIONARY_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken( + UmbDictionaryTreeStore.name +); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/manifests.ts new file mode 100644 index 0000000000..3e900b29a5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/manifests.ts @@ -0,0 +1,13 @@ +import { UmbDictionaryRepository } from '../repository/dictionary.repository'; +import { ManifestRepository } from 'libs/extensions-registry/repository.models'; + +export const DICTIONARY_REPOSITORY_ALIAS = 'Umb.Repository.Dictionary'; + +const repository: ManifestRepository = { + type: 'repository', + alias: DICTIONARY_REPOSITORY_ALIAS, + name: 'Dictionary Repository', + class: UmbDictionaryRepository, +}; + +export const manifests = [repository]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.detail.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.detail.server.data.ts new file mode 100644 index 0000000000..ba30ab03fb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.detail.server.data.ts @@ -0,0 +1,153 @@ +import { DictionaryDetailDataSource } from './dictionary.details.server.data.interface'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; +import { + DictionaryItemCreateModel, + DictionaryResource, + LanguageResource, + ProblemDetailsModel, +} from '@umbraco-cms/backend-api'; +import type { DictionaryDetails } from '@umbraco-cms/models'; + +/** + * @description - A data source for the Dictionary detail that fetches data from the server + * @export + * @class UmbDictionaryDetailServerDataSource + * @implements {DictionaryDetailDataSource} + */ +export class UmbDictionaryDetailServerDataSource implements DictionaryDetailDataSource { + #host: UmbControllerHostInterface; + + constructor(host: UmbControllerHostInterface) { + this.#host = host; + } + + /** + * @description - Creates a new Dictionary scaffold + * @param {string} parentKey + * @return {*} + * @memberof UmbDictionaryDetailServerDataSource + */ + async createScaffold(parentKey: string) { + const data: DictionaryDetails = { + name: '', + parentKey, + } as DictionaryDetails; + + return { data }; + } + + /** + * @description - Fetches a Dictionary with the given key from the server + * @param {string} key + * @return {*} + * @memberof UmbDictionaryDetailServerDataSource + */ + get(key: string) { + return tryExecuteAndNotify(this.#host, DictionaryResource.getDictionaryByKey({ key })) as any; + } + + /** + * @description - Get the dictionary overview + * @param {number?} skip + * @param {number?} take + * @returns {*} + */ + list(skip = 0, take = 1000) { + return tryExecuteAndNotify(this.#host, DictionaryResource.getDictionary({ skip, take })); + } + + /** + * @description - Updates a Dictionary on the server + * @param {DictionaryDetails} dictionary + * @return {*} + * @memberof UmbDictionaryDetailServerDataSource + */ + async update(dictionary: DictionaryDetails) { + if (!dictionary.key) { + const error: ProblemDetailsModel = { title: 'Dictionary key is missing' }; + return { error }; + } + + const payload = { key: dictionary.key, requestBody: dictionary }; + return tryExecuteAndNotify(this.#host, DictionaryResource.putDictionaryByKey(payload)); + } + + /** + * @description - Inserts a new Dictionary on the server + * @param {DictionaryDetails} data + * @return {*} + * @memberof UmbDictionaryDetailServerDataSource + */ + async insert(data: DictionaryDetails) { + const requestBody: DictionaryItemCreateModel = { + parentKey: data.parentKey, + name: data.name, + }; + + return tryExecuteAndNotify(this.#host, DictionaryResource.postDictionary({ requestBody })); + } + + /** + * @description - Deletes a Dictionary on the server + * @param {string} key + * @return {*} + * @memberof UmbDictionaryDetailServerDataSource + */ + async delete(key: string) { + if (!key) { + const error: ProblemDetailsModel = { title: 'Key is missing' }; + return { error }; + } + + return await tryExecuteAndNotify(this.#host, DictionaryResource.deleteDictionaryByKey({ key })); + } + + /** + * @description - Import a dictionary + * @param {string} fileName + * @param {string?} parentKey + * @returns {*} + * @memberof UmbDictionaryDetailServerDataSource + */ + async import(fileName: string, parentKey?: string) { + // TODO => parentKey will be a guid param once #13786 is merged and API regenerated + return await tryExecuteAndNotify( + this.#host, + DictionaryResource.postDictionaryImport({ requestBody: { fileName, parentKey } }) + ); + } + + /** + * @description - Upload a Dictionary + * @param {FormData} formData + * @return {*} + * @memberof UmbDictionaryDetailServerDataSource + */ + async upload(formData: FormData) { + return await tryExecuteAndNotify( + this.#host, + DictionaryResource.postDictionaryUpload({ + requestBody: formData, + }) + ); + } + + /** + * @description - Export a Dictionary, optionally including child items. + * @param {string} key + * @param {boolean} includeChildren + * @return {*} + * @memberof UmbDictionaryDetailServerDataSource + */ + async export(key: string, includeChildren: boolean) { + return await tryExecuteAndNotify(this.#host, DictionaryResource.getDictionaryByKeyExport({ key, includeChildren })); + } + + async getLanguages() { + // TODO => temp until language service exists. Need languages as the dictionary response + // includes the translated iso codes only, no friendly names and no way to tell if a dictionary + // is missing a translation + return await tryExecuteAndNotify(this.#host, LanguageResource.getLanguage({ skip: 0, take: 1000 })); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.details.server.data.interface.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.details.server.data.interface.ts new file mode 100644 index 0000000000..238702d0bc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.details.server.data.interface.ts @@ -0,0 +1,21 @@ +import { + DictionaryItemModel, + DictionaryUploadModel, + PagedDictionaryOverviewModel, + PagedLanguageModel, +} from '@umbraco-cms/backend-api'; +import type { DataSourceResponse, DictionaryDetails } from '@umbraco-cms/models'; + +export interface DictionaryDetailDataSource { + createScaffold(parentKey: string): Promise>; + list(skip?: number, take?: number): Promise>; + get(key: string): Promise>; + insert(data: DictionaryDetails): Promise; + update(dictionary: DictionaryItemModel): Promise; + delete(key: string): Promise; + export(key: string, includeChildren: boolean): Promise>; + import(fileName: string, parentKey?: string): Promise>; + upload(formData: FormData): Promise>; + // TODO - temp only + getLanguages(): Promise>; +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.tree.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.tree.server.data.ts new file mode 100644 index 0000000000..faacf9512b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.tree.server.data.ts @@ -0,0 +1,72 @@ +import { DictionaryResource, ProblemDetailsModel } from '@umbraco-cms/backend-api'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { RepositoryTreeDataSource } from '@umbraco-cms/repository'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; + +/** + * A data source for the Dictionary tree that fetches data from the server + * @export + * @class DictionaryTreeServerDataSource + * @implements {DictionaryTreeDataSource} + */ +export class DictionaryTreeServerDataSource implements RepositoryTreeDataSource { + #host: UmbControllerHostInterface; + + /** + * Creates an instance of DictionaryTreeDataSource. + * @param {UmbControllerHostInterface} host + * @memberof DictionaryTreeDataSource + */ + constructor(host: UmbControllerHostInterface) { + this.#host = host; + } + + /** + * Fetches the root items for the tree from the server + * @return {*} + * @memberof DictionaryTreeServerDataSource + */ + async getRootItems() { + return tryExecuteAndNotify(this.#host, DictionaryResource.getTreeDictionaryRoot({})); + } + + /** + * Fetches the children of a given parent key from the server + * @param {(string | null)} parentKey + * @return {*} + * @memberof DictionaryTreeServerDataSource + */ + async getChildrenOf(parentKey: string | null) { + if (!parentKey) { + const error: ProblemDetailsModel = { title: 'Parent key is missing' }; + return { error }; + } + + return tryExecuteAndNotify( + this.#host, + DictionaryResource.getTreeDictionaryChildren({ + parentKey, + }) + ); + } + + /** + * Fetches the items for the given keys from the server + * @param {Array} keys + * @return {*} + * @memberof DictionaryTreeServerDataSource + */ + async getItems(keys: Array) { + if (!keys || keys.length === 0) { + const error: ProblemDetailsModel = { title: 'Keys are missing' }; + return { error }; + } + + return tryExecuteAndNotify( + this.#host, + DictionaryResource.getTreeDictionaryItem({ + key: keys, + }) + ); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/sidebar-menu-item/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/sidebar-menu-item/manifests.ts index c172609271..214391bf88 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/sidebar-menu-item/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/sidebar-menu-item/manifests.ts @@ -8,8 +8,9 @@ const sidebarMenuItem: ManifestSidebarMenuItem = { loader: () => import('./dictionary-sidebar-menu-item.element'), meta: { label: 'Dictionary', - icon: 'umb:folder', + icon: 'umb:book-alt', sections: ['Umb.Section.Translation'], + entityType: 'dictionary-item' }, }; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/tree/manifests.ts index 8ba7f7d41e..2d1129154a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/tree/manifests.ts @@ -1,17 +1,13 @@ -import { UMB_DICTIONARY_TREE_STORE_CONTEXT_TOKEN } from '../dictionary.tree.store'; -import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models'; - -const treeAlias = 'Umb.Tree.Dictionary'; +import { UmbDictionaryRepository } from '../repository/dictionary.repository'; +import type { ManifestTree } from '@umbraco-cms/models'; const tree: ManifestTree = { type: 'tree', - alias: treeAlias, + alias: 'Umb.Tree.Dictionary', name: 'Dictionary Tree', meta: { - storeAlias: UMB_DICTIONARY_TREE_STORE_CONTEXT_TOKEN.toString(), + repository: UmbDictionaryRepository, }, }; -const treeItemActions: Array = []; - -export const manifests = [tree, ...treeItemActions]; +export const manifests = [tree]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts new file mode 100644 index 0000000000..36824bb5a0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.context.ts @@ -0,0 +1,84 @@ +import { UmbDictionaryRepository } from '../repository/dictionary.repository'; +import { UmbWorkspaceContext } from '../../../../backoffice/shared/components/workspace/workspace-context/workspace-context'; +import { UmbWorkspaceEntityContextInterface } from '../../../../backoffice/shared/components/workspace/workspace-context/workspace-entity-context.interface'; +import { UmbControllerHostInterface } from '@umbraco-cms/controller'; +import { ObjectState } from '@umbraco-cms/observable-api'; +import type { DictionaryDetails } from '@umbraco-cms/models'; + +type EntityType = DictionaryDetails; +export class UmbWorkspaceDictionaryContext + extends UmbWorkspaceContext + implements UmbWorkspaceEntityContextInterface +{ + #host: UmbControllerHostInterface; + #repo: UmbDictionaryRepository; + + #data = new ObjectState(undefined); + data = this.#data.asObservable(); + name = this.#data.getObservablePart((data) => data?.name); + dictionary = this.#data.getObservablePart((data) => data); + + constructor(host: UmbControllerHostInterface) { + super(host); + this.#host = host; + this.#repo = new UmbDictionaryRepository(this.#host); + } + + getData() { + return this.#data.getValue(); + } + + getEntityKey() { + return this.getData()?.key || ''; + } + + getEntityType() { + return 'dictionary-item'; + } + + setName(name: string) { + this.#data.update({ name }); + } + + setPropertyValue(isoCode: string, translation: string) { + if (!this.#data.value) return; + + // update if the code already exists + const updatedValue = + this.#data.value.translations?.map((translationItem) => { + if (translationItem.isoCode === isoCode) { + return { ...translationItem, translation}; + } + return translationItem; + }) ?? []; + + // if code doesn't exist, add it to the new value set + if (!updatedValue?.find((x) => x.isoCode === isoCode)) { + updatedValue?.push({ isoCode, translation }); + } + + this.#data.next({ ...this.#data.value, translations: updatedValue }); + } + + async load(entityKey: string) { + const { data } = await this.#repo.requestDetails(entityKey); + if (data) { + this.#data.next(data); + } + } + + async createScaffold(parentKey: string | null) { + const { data } = await this.#repo.createDetailsScaffold(parentKey); + if (!data) return; + this.#data.next(data); + } + + async save() { + if (!this.#data.value) return; + this.#repo.saveDetail(this.#data.value); + } + + public destroy(): void { + this.#data.complete(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.element.ts index 1ee69d6156..74e6564ca6 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.element.ts @@ -1,26 +1,74 @@ +import { UUIInputElement, UUIInputEvent } from '@umbraco-ui/uui'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement } from 'lit'; -import { customElement, property } from 'lit/decorators.js'; +import { css, html } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; +import { UmbWorkspaceEntityElement } from '../../../../backoffice/shared/components/workspace/workspace-entity-element.interface'; +import { UmbWorkspaceDictionaryContext } from './dictionary-workspace.context'; +import { UmbLitElement } from '@umbraco-cms/element'; -@customElement('umb-workspace-dictionary') -export class UmbWorkspaceDictionaryElement extends LitElement { +@customElement('umb-dictionary-workspace') +export class UmbWorkspaceDictionaryElement extends UmbLitElement implements UmbWorkspaceEntityElement { static styles = [ UUITextStyles, css` - :host { - display: block; + #header { + display: flex; + padding: 0 var(--uui-size-space-6); + gap: var(--uui-size-space-4); + width: 100%; + } + uui-input { width: 100%; - height: 100%; } `, ]; - @property() - id!: string; + @state() + _unique?: string; + + public load(entityKey: string) { + this.#workspaceContext.load(entityKey); + this._unique = entityKey; + } + + public create(parentKey: string | null) { + this.#workspaceContext.createScaffold(parentKey); + } + + @state() + private _name?: string | null = ''; + + #workspaceContext = new UmbWorkspaceDictionaryContext(this); + + async connectedCallback() { + super.connectedCallback(); + + this.observe(this.#workspaceContext.name, (name) => { + this._name = name; + }); + } + + // TODO. find a way where we don't have to do this for all workspaces. + #handleInput(event: UUIInputEvent) { + if (event instanceof UUIInputEvent) { + const target = event.composedPath()[0] as UUIInputElement; + + if (typeof target?.value === 'string') { + this.#workspaceContext?.setName(target.value); + } + } + } render() { return html` - Dictionary Workspace + + + `; } } @@ -29,6 +77,6 @@ export default UmbWorkspaceDictionaryElement; declare global { interface HTMLElementTagNameMap { - 'umb-workspace-dictionary': UmbWorkspaceDictionaryElement; + 'umb-dictionary-workspace': UmbWorkspaceDictionaryElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.stories.ts new file mode 100644 index 0000000000..71a1059e42 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/dictionary-workspace.stories.ts @@ -0,0 +1,16 @@ +import './dictionary-workspace.element'; +import { Meta, Story } from '@storybook/web-components'; +import { html } from 'lit-html'; +import { data } from '../../../../core/mocks/data/dictionary.data'; +import type { UmbWorkspaceDictionaryElement } from './dictionary-workspace.element'; + +export default { + title: 'Workspaces/Dictionary', + component: 'umb-dictionary-workspace', + id: 'umb-dictionary-workspace', +} as Meta; + +export const AAAOverview: Story = () => + html` `; + +AAAOverview.storyName = 'Overview'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/manifests.ts index c354084f1c..55b41e8202 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/manifests.ts @@ -1,4 +1,6 @@ -import type { ManifestWorkspace } from '@umbraco-cms/models'; +import { DICTIONARY_REPOSITORY_ALIAS } from '../repository/manifests'; +import { UmbSaveWorkspaceAction } from '../../../../backoffice/shared/workspace-actions/save.action'; +import type { ManifestWorkspace, ManifestWorkspaceAction, ManifestWorkspaceView } from '@umbraco-cms/models'; const workspaceAlias = 'Umb.Workspace.Dictionary'; @@ -8,8 +10,41 @@ const workspace: ManifestWorkspace = { name: 'Dictionary Workspace', loader: () => import('./dictionary-workspace.element'), meta: { - entityType: 'dictionary', + entityType: 'dictionary-item', }, }; -export const manifests = [workspace]; +const workspaceViews: Array = [ + { + type: 'workspaceView', + alias: 'Umb.WorkspaceView.Dictionary.Edit', + name: 'Dictionary Workspace Edit View', + loader: () => import('./views/edit/workspace-view-dictionary-edit.element'), + weight: 100, + meta: { + workspaces: [workspaceAlias], + label: 'Edit', + pathname: 'edit', + icon: 'edit', + }, + }, +]; + +const workspaceActions: Array = [ + { + type: 'workspaceAction', + alias: 'Umb.WorkspaceAction.Dictionary.Save', + name: 'Save Dictionary Workspace Action', + weight: 90, + meta: { + workspaces: ['Umb.Workspace.Dictionary'], + label: 'Save', + look: 'primary', + color: 'positive', + repositoryAlias: DICTIONARY_REPOSITORY_ALIAS, + api: UmbSaveWorkspaceAction, + }, + }, +]; + +export const manifests = [workspace, ...workspaceViews, ...workspaceActions]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/views/edit/workspace-view-dictionary-edit.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/views/edit/workspace-view-dictionary-edit.element.ts new file mode 100644 index 0000000000..04152ba8a9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/views/edit/workspace-view-dictionary-edit.element.ts @@ -0,0 +1,98 @@ +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { css, html } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; +import { repeat } from 'lit/directives/repeat.js'; +import { ifDefined } from 'lit-html/directives/if-defined.js'; +import { UUITextareaElement, UUITextareaEvent } from '@umbraco-ui/uui'; +import { UmbWorkspaceDictionaryContext } from '../../dictionary-workspace.context'; +import { UmbDictionaryRepository } from '../../../repository/dictionary.repository'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { DictionaryItemModel, LanguageModel } from '@umbraco-cms/backend-api'; + +@customElement('umb-workspace-view-dictionary-edit') +export class UmbWorkspaceViewDictionaryEditElement extends UmbLitElement { + static styles = [ + UUITextStyles, + css` + :host { + display: block; + margin: var(--uui-size-layout-1); + } + `, + ]; + + @state() + private _dictionary?: DictionaryItemModel; + + #repo!: UmbDictionaryRepository; + + @state() + private _languages: Array = []; + + #workspaceContext!: UmbWorkspaceDictionaryContext; + + async connectedCallback() { + super.connectedCallback(); + + this.#repo = new UmbDictionaryRepository(this); + this._languages = await this.#repo.getLanguages(); + + this.consumeContext('umbWorkspaceContext', (_instance) => { + this.#workspaceContext = _instance; + this.#observeDictionary(); + }); + } + + #observeDictionary() { + this.observe(this.#workspaceContext.dictionary, (dictionary) => { + this._dictionary = dictionary; + }); + } + + #renderTranslation(language: LanguageModel) { + if (!language.isoCode) return; + + const translation = this._dictionary?.translations?.find((x) => x.isoCode === language.isoCode); + + return html` + + `; + } + + #onTextareaChange(e: Event) { + if (e instanceof UUITextareaEvent) { + const target = e.composedPath()[0] as UUITextareaElement; + const translation = target.value.toString(); + const isoCode = target.getAttribute('name')!; + + this.#workspaceContext.setPropertyValue(isoCode, translation); + } + } + + render() { + return html` + +

Edit the different language versions for the dictionary item '${this._dictionary?.name}' below.

+ + ${repeat( + this._languages, + (item) => item.isoCode, + (item) => this.#renderTranslation(item) + )} +
+ `; + } +} + +export default UmbWorkspaceViewDictionaryEditElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-workspace-view-dictionary-edit': UmbWorkspaceViewDictionaryEditElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/views/edit/workspace-view-dictionary-edit.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/views/edit/workspace-view-dictionary-edit.stories.ts new file mode 100644 index 0000000000..806bd714e5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/workspace/views/edit/workspace-view-dictionary-edit.stories.ts @@ -0,0 +1,25 @@ +import { Meta, Story } from '@storybook/web-components'; +import { html } from 'lit-html'; +//import { data } from '../../../../../core/mocks/data/dictionary.data'; +import type { UmbWorkspaceViewDictionaryEditElement } from './workspace-view-dictionary-edit.element'; +import './workspace-view-dictionary-edit.element'; +//import { UmbWorkspaceDictionaryContext } from '../../workspace-dictionary.context'; + +export default { + title: 'Workspaces/Dictionary/Views/Edit', + component: 'umb-workspace-view-dictionary-edit', + id: 'umb-workspace-view-dictionary-edit', + decorators: [ + (story) => { + return html`TODO: make use of mocked workspace context??`; + /*html` + ${story()} + `,*/ + } + ], +} as Meta; + +export const AAAOverview: Story = () => + html` `; + +AAAOverview.storyName = 'Overview'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/section.manifest.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/section.manifest.ts index 0b0deaadd9..6e60134aa8 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/section.manifest.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/section.manifest.ts @@ -1,4 +1,4 @@ -import type { ManifestSection } from '@umbraco-cms/models'; +import type { ManifestDashboard, ManifestSection } from '@umbraco-cms/models'; const sectionAlias = 'Umb.Section.Translation'; @@ -13,4 +13,20 @@ const section: ManifestSection = { }, }; -export const manifests = [section]; +const dashboards: Array = [ + { + type: 'dashboard', + alias: 'Umb.Dashboard.TranslationDictionary', + name: 'Dictionary Translation Dashboard', + elementName: 'umb-dashboard-translation-dictionary', + loader: () => import('./dashboards/dictionary/dashboard-translation-dictionary.element'), + meta: { + label: 'Dictionary overview', + sections: [sectionAlias], + pathname: '', + }, + }, +]; + + +export const manifests = [section, ...dashboards]; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/data/dictionary.data.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/data/dictionary.data.ts index e2d8f9fec5..4c4ea991be 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/data/dictionary.data.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/data/dictionary.data.ts @@ -7,20 +7,36 @@ export const data: Array = [ { parentKey: null, name: 'Hello', - key: 'b7e7d0ab-53ba-485d-b8bd-12537f9925cb', + key: 'aae7d0ab-53ba-485d-b8bd-12537f9925cb', hasChildren: true, - type: 'dictionary', + type: 'dictionary-item', isContainer: false, - icon: 'umb:icon-book-alt', + icon: 'umb:book-alt', + translations: [{ + isoCode: 'en', + translation: 'hello in en-US' + }, + { + isoCode: 'fr', + translation: '', + }], }, { - parentKey: 'b7e7d0ab-53ba-485d-b8bd-12537f9925cb', - name: 'Hello', - key: 'b7e7d0ab-53bb-485d-b8bd-12537f9925cb', + parentKey: 'aae7d0ab-53ba-485d-b8bd-12537f9925cb', + name: 'Hello again', + key: 'bbe7d0ab-53bb-485d-b8bd-12537f9925cb', hasChildren: false, - type: 'dictionary', + type: 'dictionary-item', isContainer: false, - icon: 'umb:icon-book-alt', + icon: 'umb:book-alt', + translations: [{ + isoCode: 'en', + translation: 'Hello again in en-US' + }, + { + isoCode: 'fr', + translation: 'Hello in fr' + }], }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/data/entity.data.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/data/entity.data.ts index 700be3b625..29d9fc8f09 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/data/entity.data.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/data/entity.data.ts @@ -7,6 +7,10 @@ export class UmbEntityData extends UmbData { super(data); } + getList(skip: number, take: number) { + return this.data.slice(skip, skip + take); + } + getByKey(key: string) { return this.data.find((item) => item.key === key); } diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/dictionary.handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/dictionary.handlers.ts index f73e8c29cf..d3b99f6c88 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/dictionary.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/dictionary.handlers.ts @@ -1,13 +1,124 @@ import { rest } from 'msw'; +import { v4 as uuidv4 } from 'uuid'; import { umbDictionaryData } from '../data/dictionary.data'; +import { CreatedResultModel, DictionaryImportModel, DictionaryOverviewModel } from '@umbraco-cms/backend-api'; +import type { DictionaryDetails } from '@umbraco-cms/models'; + +const uploadResponse: DictionaryImportModel = { + fileName: 'c:/path/to/tempfilename.udt', + parentKey: 'b7e7d0ab-53ba-485d-dddd-12537f9925aa', +}; + +/// +const importResponse: DictionaryDetails = { + parentKey: null, + name: 'Uploaded dictionary', + key: 'b7e7d0ab-53ba-485d-dddd-12537f9925cb', + hasChildren: false, + type: 'dictionary-item', + isContainer: false, + icon: 'umb:book-alt', + translations: [{ + isoCode: 'en', + translation: 'I am an imported US value' + }, + { + isoCode: 'fr', + translation: 'I am an imported French value', + }], +}; + + +// alternate data for dashboard view +const overviewData: Array = [ + { + name: 'Hello', + key: 'aae7d0ab-53ba-485d-b8bd-12537f9925cb', + translatedIsoCodes: ['en'], + }, + { + name: 'Hello again', + key: 'bbe7d0ab-53bb-485d-b8bd-12537f9925cb', + translatedIsoCodes: ['en', 'fr'], + }, +]; // TODO: add schema export const handlers = [ - rest.get('/umbraco/management/api/v1/tree/dictionary/root', (req, res, ctx) => { - const rootItems = umbDictionaryData.getTreeRoot(); + rest.get('/umbraco/management/api/v1/dictionary/:key', (req, res, ctx) => { + const key = req.params.key as string; + if (!key) return; + + const dictionary = umbDictionaryData.getByKey(key); + console.log(dictionary); + return res(ctx.status(200), ctx.json(dictionary)); + }), + + rest.get('/umbraco/management/api/v1/dictionary', (req, res, ctx) => { + const skip = req.url.searchParams.get('skip'); + const take = req.url.searchParams.get('take'); + if (!skip || !take) return; + + // overview is DictionaryOverview[], umbDictionaryData provides DictionaryDetails[] + // which are incompatible types to mock, so we can do a filthy replacement here + //const items = umbDictionaryData.getList(parseInt(skip), parseInt(take)); + const items = overviewData; + const response = { - total: rootItems.length, - items: rootItems, + total: items.length, + items, + }; + + return res(ctx.status(200), ctx.json(response)); + }), + + rest.post('/umbraco/management/api/v1/dictionary', async (req, res, ctx) => { + const data = await req.json(); + if (!data) return; + + data.parentKey = data.parentId; + data.icon = 'umb:book-alt'; + data.hasChildren = false; + data.type = 'dictionary-item'; + data.translations = [ + { + isoCode: 'en-US', + translation: '', + }, + { + isoCode: 'fr', + translation: '', + }, + ]; + + const value = umbDictionaryData.save([data])[0]; + + const createdResult: CreatedResultModel = { + value, + statusCode: 200, + }; + + return res(ctx.status(200), ctx.json(createdResult)); + }), + + rest.patch('/umbraco/management/api/v1/dictionary/:key', async (req, res, ctx) => { + const data = await req.json(); + if (!data) return; + + const key = req.params.key as string; + if (!key) return; + + const dataToSave = JSON.parse(data[0].value); + const saved = umbDictionaryData.save([dataToSave]); + + return res(ctx.status(200), ctx.json(saved)); + }), + + rest.get('/umbraco/management/api/v1/tree/dictionary/root', (req, res, ctx) => { + const items = umbDictionaryData.getTreeRoot(); + const response = { + total: items.length, + items, }; return res(ctx.status(200), ctx.json(response)); }), @@ -16,11 +127,11 @@ export const handlers = [ const parentKey = req.url.searchParams.get('parentKey'); if (!parentKey) return; - const children = umbDictionaryData.getTreeItemChildren(parentKey); + const items = umbDictionaryData.getTreeItemChildren(parentKey); const response = { - total: children.length, - items: children, + total: items.length, + items, }; return res(ctx.status(200), ctx.json(response)); @@ -34,4 +145,53 @@ export const handlers = [ return res(ctx.status(200), ctx.json(items)); }), + + rest.delete('/umbraco/management/api/v1/dictionary/:key', (req, res, ctx) => { + const key = req.params.key as string; + if (!key) return; + + const deletedKeys = umbDictionaryData.delete([key]); + + return res(ctx.status(200), ctx.json(deletedKeys)); + }), + + // TODO => handle properly, querystring breaks handler + rest.get('/umbraco/management/api/v1/dictionary/:key/export', (req, res, ctx) => { + const key = req.params.key as string; + if (!key) return; + + const includeChildren = req.url.searchParams.get('includeChildren'); + const item = umbDictionaryData.getByKey(key); + + alert(`Downloads file for dictionary "${item?.name}", ${includeChildren === 'true' ? 'with' : 'without'} children.`) + return res(ctx.status(200)); + }), + + rest.post('/umbraco/management/api/v1/dictionary/upload', async (req, res, ctx) => { + if (!req.arrayBuffer()) return; + + return res(ctx.status(200), ctx.json(uploadResponse)); + }), + + rest.post('/umbraco/management/api/v1/dictionary/import', async (req, res, ctx) => { + const file = req.url.searchParams.get('file'); + + if (!file) return; + + importResponse.parentKey = req.url.searchParams.get('parentId') ?? null; + umbDictionaryData.save([importResponse]); + + // build the path to the new item => reflects the expected server response + const path = ['-1']; + if (importResponse.parentKey) path.push(importResponse.parentKey); + + path.push(importResponse.key); + + const contentResult = { + content: path.join(','), + statusCode: 200, + }; + + return res(ctx.status(200), ctx.json(contentResult)); + }), ];