Merge branch 'main' into feature/block-grid-editor-inline-mode

This commit is contained in:
Niels Lyngsø
2024-02-27 09:08:20 +01:00
committed by GitHub
59 changed files with 939 additions and 108 deletions

View File

@@ -127,6 +127,7 @@
"example": "node ./devops/example-runner/index.js",
"format:fix": "npm run format -- --write",
"format": "prettier 'src/**/*.ts' -- check",
"generate:api-local": "openapi --input ../Umbraco.Cms.Api.Management/OpenApi.json --output src/external/backend-api/src --postfixServices Resource --useOptions",
"generate:api-dev": "openapi --input http://localhost:11000/umbraco/swagger/management/swagger.json --output src/external/backend-api/src --postfixServices Resource --useOptions",
"generate:api": "openapi --input https://raw.githubusercontent.com/umbraco/Umbraco-CMS/v14/dev/src/Umbraco.Cms.Api.Management/OpenApi.json --output src/external/backend-api/src --postfixServices Resource --useOptions",
"generate:icons": "node ./devops/icons/index.js",

View File

@@ -76,6 +76,7 @@ export type { CreateTemplateRequestModel } from './models/CreateTemplateRequestM
export type { CreateUserGroupRequestModel } from './models/CreateUserGroupRequestModel';
export type { CreateUserRequestModel } from './models/CreateUserRequestModel';
export type { CreateUserResponseModel } from './models/CreateUserResponseModel';
export type { CreateWebhookRequestModel } from './models/CreateWebhookRequestModel';
export type { CultureAndScheduleRequestModel } from './models/CultureAndScheduleRequestModel';
export type { CultureReponseModel } from './models/CultureReponseModel';
export type { CurrentUserResponseModel } from './models/CurrentUserResponseModel';
@@ -361,6 +362,7 @@ export type { TemplateQueryResultItemPresentationModel } from './models/Template
export type { TemplateQueryResultResponseModel } from './models/TemplateQueryResultResponseModel';
export type { TemplateQuerySettingsResponseModel } from './models/TemplateQuerySettingsResponseModel';
export type { TemplateResponseModel } from './models/TemplateResponseModel';
export type { TemporaryFileConfigurationResponseModel } from './models/TemporaryFileConfigurationResponseModel';
export type { TemporaryFileResponseModel } from './models/TemporaryFileResponseModel';
export type { TourStatusModel } from './models/TourStatusModel';
export type { TreeItemPresentationModel } from './models/TreeItemPresentationModel';
@@ -399,6 +401,7 @@ export type { UpdateTemplateRequestModel } from './models/UpdateTemplateRequestM
export type { UpdateUserGroupRequestModel } from './models/UpdateUserGroupRequestModel';
export type { UpdateUserGroupsOnUserRequestModel } from './models/UpdateUserGroupsOnUserRequestModel';
export type { UpdateUserRequestModel } from './models/UpdateUserRequestModel';
export type { UpdateWebhookRequestModel } from './models/UpdateWebhookRequestModel';
export type { UpgradeSettingsResponseModel } from './models/UpgradeSettingsResponseModel';
export type { UserConfigurationResponseModel } from './models/UserConfigurationResponseModel';
export type { UserGroupBaseModel } from './models/UserGroupBaseModel';
@@ -422,6 +425,10 @@ export type { VariantModelBaseModel } from './models/VariantModelBaseModel';
export type { VariantResponseModelBaseModel } from './models/VariantResponseModelBaseModel';
export type { VerifyInviteUserRequestModel } from './models/VerifyInviteUserRequestModel';
export type { VerifyResetPasswordTokenRequestModel } from './models/VerifyResetPasswordTokenRequestModel';
export type { WebhookEventResponseModel } from './models/WebhookEventResponseModel';
export type { WebhookItemResponseModel } from './models/WebhookItemResponseModel';
export type { WebhookModelBaseModel } from './models/WebhookModelBaseModel';
export type { WebhookResponseModel } from './models/WebhookResponseModel';
export { AuditLogResource } from './services/AuditLogResource';
export { CultureResource } from './services/CultureResource';
@@ -468,3 +475,4 @@ export { TrackedReferenceResource } from './services/TrackedReferenceResource';
export { UpgradeResource } from './services/UpgradeResource';
export { UserResource } from './services/UserResource';
export { UserGroupResource } from './services/UserGroupResource';
export { WebhookResource } from './services/WebhookResource';

View File

@@ -6,9 +6,11 @@
import type { CreateContentTypeForMediaTypeRequestModel } from './CreateContentTypeForMediaTypeRequestModel';
import type { MediaTypeCompositionModel } from './MediaTypeCompositionModel';
import type { MediaTypeSortModel } from './MediaTypeSortModel';
import type { ReferenceByIdModel } from './ReferenceByIdModel';
export type CreateMediaTypeRequestModel = (CreateContentTypeForMediaTypeRequestModel & {
allowedMediaTypes: Array<MediaTypeSortModel>;
compositions: Array<MediaTypeCompositionModel>;
collection?: ReferenceByIdModel | null;
});

View File

@@ -0,0 +1,12 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { WebhookModelBaseModel } from './WebhookModelBaseModel';
export type CreateWebhookRequestModel = (WebhookModelBaseModel & {
id?: string | null;
events: Array<string>;
});

View File

@@ -10,5 +10,6 @@ export type DataTypeResponseModel = (DataTypeModelBaseModel & {
id: string;
parent?: ReferenceByIdModel | null;
isDeletable: boolean;
canIgnoreStartNodes: boolean;
});

View File

@@ -0,0 +1,12 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type TemporaryFileConfigurationResponseModel = {
imageFileTypes: Array<string>;
disallowedUploadedFilesExtensions: Array<string>;
allowedUploadedFileExtensions: Array<string>;
maxFileSize?: number | null;
};

View File

@@ -0,0 +1,11 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { WebhookModelBaseModel } from './WebhookModelBaseModel';
export type UpdateWebhookRequestModel = (WebhookModelBaseModel & {
events: Array<string>;
});

View File

@@ -0,0 +1,11 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type WebhookEventResponseModel = {
eventName: string;
eventType: string;
alias: string;
};

View File

@@ -0,0 +1,13 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type WebhookItemResponseModel = {
enabled: boolean;
name: string;
events: string;
url: string;
types: string;
};

View File

@@ -0,0 +1,12 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type WebhookModelBaseModel = {
enabled: boolean;
url: string;
contentTypeKeys: Array<string>;
headers: Record<string, string>;
};

View File

@@ -0,0 +1,13 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { WebhookEventResponseModel } from './WebhookEventResponseModel';
import type { WebhookModelBaseModel } from './WebhookModelBaseModel';
export type WebhookResponseModel = (WebhookModelBaseModel & {
id: string;
events: Array<WebhookEventResponseModel>;
});

View File

@@ -11,12 +11,14 @@ import type { DocumentNotificationResponseModel } from '../models/DocumentNotifi
import type { DocumentResponseModel } from '../models/DocumentResponseModel';
import type { DomainsResponseModel } from '../models/DomainsResponseModel';
import type { MoveDocumentRequestModel } from '../models/MoveDocumentRequestModel';
import type { MoveMediaRequestModel } from '../models/MoveMediaRequestModel';
import type { PagedDocumentCollectionResponseModel } from '../models/PagedDocumentCollectionResponseModel';
import type { PagedDocumentRecycleBinItemResponseModel } from '../models/PagedDocumentRecycleBinItemResponseModel';
import type { PagedDocumentTreeItemResponseModel } from '../models/PagedDocumentTreeItemResponseModel';
import type { PublicAccessRequestModel } from '../models/PublicAccessRequestModel';
import type { PublishDocumentRequestModel } from '../models/PublishDocumentRequestModel';
import type { PublishDocumentWithDescendantsRequestModel } from '../models/PublishDocumentWithDescendantsRequestModel';
import type { ReferenceByIdModel } from '../models/ReferenceByIdModel';
import type { SortingRequestModel } from '../models/SortingRequestModel';
import type { UnpublishDocumentRequestModel } from '../models/UnpublishDocumentRequestModel';
import type { UpdateDocumentNotificationsRequestModel } from '../models/UpdateDocumentNotificationsRequestModel';
@@ -225,7 +227,7 @@ export class DocumentResource {
}
/**
* @returns any Success
* @returns void
* @throws ApiError
*/
public static putDocumentByIdDomains({
@@ -234,7 +236,7 @@ export class DocumentResource {
}: {
id: string,
requestBody?: UpdateDomainsRequestModel,
}): CancelablePromise<any> {
}): CancelablePromise<void> {
return __request(OpenAPI, {
method: 'PUT',
url: '/umbraco/management/api/v1/document/{id}/domains',
@@ -244,7 +246,10 @@ export class DocumentResource {
body: requestBody,
mediaType: 'application/json',
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
404: `Not Found`,
409: `Conflict`,
},
});
}
@@ -683,6 +688,58 @@ export class DocumentResource {
});
}
/**
* @returns any Success
* @throws ApiError
*/
public static getRecycleBinDocumentByIdOriginalParent({
id,
}: {
id: string,
}): CancelablePromise<ReferenceByIdModel> {
return __request(OpenAPI, {
method: 'GET',
url: '/umbraco/management/api/v1/recycle-bin/document/{id}/original-parent',
path: {
'id': id,
},
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
403: `The authenticated user do not have access to this resource`,
404: `Not Found`,
},
});
}
/**
* @returns any Success
* @throws ApiError
*/
public static putRecycleBinDocumentByIdRestore({
id,
requestBody,
}: {
id: string,
requestBody?: MoveMediaRequestModel,
}): CancelablePromise<any> {
return __request(OpenAPI, {
method: 'PUT',
url: '/umbraco/management/api/v1/recycle-bin/document/{id}/restore',
path: {
'id': id,
},
body: requestBody,
mediaType: 'application/json',
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
403: `The authenticated user do not have access to this resource`,
404: `Not Found`,
},
});
}
/**
* @returns PagedDocumentRecycleBinItemResponseModel Success
* @throws ApiError

View File

@@ -11,6 +11,7 @@ import type { MoveMediaRequestModel } from '../models/MoveMediaRequestModel';
import type { PagedMediaCollectionResponseModel } from '../models/PagedMediaCollectionResponseModel';
import type { PagedMediaRecycleBinItemResponseModel } from '../models/PagedMediaRecycleBinItemResponseModel';
import type { PagedMediaTreeItemResponseModel } from '../models/PagedMediaTreeItemResponseModel';
import type { ReferenceByIdModel } from '../models/ReferenceByIdModel';
import type { SortingRequestModel } from '../models/SortingRequestModel';
import type { UpdateMediaRequestModel } from '../models/UpdateMediaRequestModel';
@@ -360,6 +361,58 @@ export class MediaResource {
});
}
/**
* @returns any Success
* @throws ApiError
*/
public static getRecycleBinMediaByIdOriginalParent({
id,
}: {
id: string,
}): CancelablePromise<ReferenceByIdModel> {
return __request(OpenAPI, {
method: 'GET',
url: '/umbraco/management/api/v1/recycle-bin/media/{id}/original-parent',
path: {
'id': id,
},
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
403: `The authenticated user do not have access to this resource`,
404: `Not Found`,
},
});
}
/**
* @returns any Success
* @throws ApiError
*/
public static putRecycleBinMediaByIdRestore({
id,
requestBody,
}: {
id: string,
requestBody?: MoveMediaRequestModel,
}): CancelablePromise<any> {
return __request(OpenAPI, {
method: 'PUT',
url: '/umbraco/management/api/v1/recycle-bin/media/{id}/restore',
path: {
'id': id,
},
body: requestBody,
mediaType: 'application/json',
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
403: `The authenticated user do not have access to this resource`,
404: `Not Found`,
},
});
}
/**
* @returns PagedMediaRecycleBinItemResponseModel Success
* @throws ApiError

View File

@@ -2,6 +2,7 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { TemporaryFileConfigurationResponseModel } from '../models/TemporaryFileConfigurationResponseModel';
import type { TemporaryFileResponseModel } from '../models/TemporaryFileResponseModel';
import type { CancelablePromise } from '../core/CancelablePromise';
@@ -85,7 +86,7 @@ export class TemporaryFileResource {
* @returns any Success
* @throws ApiError
*/
public static getTemporaryFileConfiguration(): CancelablePromise<any> {
public static getTemporaryFileConfiguration(): CancelablePromise<TemporaryFileConfigurationResponseModel> {
return __request(OpenAPI, {
method: 'GET',
url: '/umbraco/management/api/v1/temporary-file/configuration',

View File

@@ -0,0 +1,132 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { CreateWebhookRequestModel } from '../models/CreateWebhookRequestModel';
import type { UpdateWebhookRequestModel } from '../models/UpdateWebhookRequestModel';
import type { WebhookItemResponseModel } from '../models/WebhookItemResponseModel';
import type { WebhookResponseModel } from '../models/WebhookResponseModel';
import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request';
export class WebhookResource {
/**
* @returns string Created
* @throws ApiError
*/
public static postWebhook({
requestBody,
}: {
requestBody?: CreateWebhookRequestModel,
}): CancelablePromise<string> {
return __request(OpenAPI, {
method: 'POST',
url: '/umbraco/management/api/v1/webhook',
body: requestBody,
mediaType: 'application/json',
responseHeader: 'Umb-Generated-Resource',
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
404: `Not Found`,
},
});
}
/**
* @returns any Success
* @throws ApiError
*/
public static getWebhookById({
id,
}: {
id: string,
}): CancelablePromise<WebhookResponseModel> {
return __request(OpenAPI, {
method: 'GET',
url: '/umbraco/management/api/v1/webhook/{id}',
path: {
'id': id,
},
errors: {
401: `The resource is protected and requires an authentication token`,
404: `Not Found`,
},
});
}
/**
* @returns any Success
* @throws ApiError
*/
public static putWebhookById({
id,
requestBody,
}: {
id: string,
requestBody?: UpdateWebhookRequestModel,
}): CancelablePromise<any> {
return __request(OpenAPI, {
method: 'PUT',
url: '/umbraco/management/api/v1/webhook/{id}',
path: {
'id': id,
},
body: requestBody,
mediaType: 'application/json',
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
404: `Not Found`,
},
});
}
/**
* @returns any Success
* @throws ApiError
*/
public static deleteWebhookById({
id,
}: {
id: string,
}): CancelablePromise<any> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/umbraco/management/api/v1/webhook/{id}',
path: {
'id': id,
},
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
404: `Not Found`,
},
});
}
/**
* @returns any Success
* @throws ApiError
*/
public static getWebhookItem({
ids,
}: {
ids?: Array<string>,
}): CancelablePromise<Array<WebhookItemResponseModel>> {
return __request(OpenAPI, {
method: 'GET',
url: '/umbraco/management/api/v1/webhook/item',
query: {
'ids': ids,
},
errors: {
401: `The resource is protected and requires an authentication token`,
},
});
}
}

View File

@@ -18,6 +18,7 @@ export const data: Array<UmbMockDataTypeModel> = [
editorAlias: '',
values: [],
isDeletable: true,
canIgnoreStartNodes: false,
},
{
name: 'Folder 2',
@@ -28,6 +29,7 @@ export const data: Array<UmbMockDataTypeModel> = [
editorAlias: '',
values: [],
isDeletable: true,
canIgnoreStartNodes: false,
},
{
id: '0cc0eba1-9960-42c9-bf9b-60e150b429ae',
@@ -39,6 +41,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
},
{
name: 'Text',
@@ -49,6 +52,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'maxChars',
@@ -65,6 +69,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [],
},
{
@@ -76,6 +81,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [],
},
{
@@ -87,6 +93,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'useLabel',
@@ -144,6 +151,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'validationLimit',
@@ -160,6 +168,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
//showPalette
@@ -198,6 +207,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'overlaySize',
@@ -230,6 +240,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'startNode',
@@ -278,6 +289,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'format',
@@ -301,6 +313,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'format',
@@ -321,6 +334,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'format',
@@ -341,6 +355,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'inputMode',
@@ -357,6 +372,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'minNumber',
@@ -377,6 +393,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'multiple',
@@ -401,6 +418,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'enableRange',
@@ -437,6 +455,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'default',
@@ -465,6 +484,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'group',
@@ -485,6 +505,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [],
},
{
@@ -496,6 +517,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'items',
@@ -516,6 +538,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'items',
@@ -536,6 +559,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'blocks',
@@ -600,6 +624,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [],
},
{
@@ -611,6 +636,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'crops',
@@ -643,6 +669,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'fileExtensions',
@@ -663,6 +690,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'blockGroups',
@@ -781,8 +809,9 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: false,
canIgnoreStartNodes: false,
values: [
{ alias: 'pageSize', value: 2 },
{ alias: 'pageSize', value: 25 },
{ alias: 'orderDirection', value: 'desc' },
{
alias: 'includeProperties',
@@ -816,6 +845,51 @@ export const data: Array<UmbMockDataTypeModel> = [
{ alias: 'useInfiniteEditor', value: true },
],
},
{
name: 'Collection View - Media',
id: '3a0156c4-3b8c-4803-bdc1-6871faa83fff',
parent: null,
editorAlias: 'Umbraco.ListView',
editorUiAlias: 'Umb.PropertyEditorUi.CollectionView',
hasChildren: false,
isFolder: false,
isDeletable: false,
canIgnoreStartNodes: false,
values: [
{ alias: 'pageSize', value: 2 },
{ alias: 'orderDirection', value: 'desc' },
{
alias: 'includeProperties',
value: [
{ alias: 'sortOrder', header: 'Sort order', isSystem: true, nameTemplate: '' },
{ alias: 'updateDate', header: 'Last edited', isSystem: true },
{ alias: 'owner', header: 'Created by', isSystem: true },
],
},
{ alias: 'orderBy', value: 'updateDate' },
{
alias: 'bulkActionPermissions',
value: {
allowBulkPublish: false,
allowBulkUnpublish: false,
allowBulkCopy: true,
allowBulkMove: true,
allowBulkDelete: true,
},
},
{
alias: 'layouts',
value: [
{ icon: 'icon-grid', isSystem: true, name: 'Grid', path: '', selected: true },
{ icon: 'icon-list', isSystem: true, name: 'Table', path: '', selected: true },
],
},
{ alias: 'icon', value: 'icon-layers' },
{ alias: 'tabName', value: 'Items' },
{ alias: 'showContentFirst', value: false },
{ alias: 'useInfiniteEditor', value: true },
],
},
{
name: 'Icon Picker',
id: 'dt-iconPicker',
@@ -825,6 +899,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [],
},
{
@@ -836,6 +911,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'hideLabel',
@@ -915,6 +991,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [],
},
{
@@ -926,6 +1003,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [],
},
{
@@ -937,6 +1015,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [
{
alias: 'step',
@@ -953,6 +1032,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [],
},
{
@@ -964,6 +1044,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [],
},
{
@@ -975,6 +1056,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [],
},
{
@@ -986,6 +1068,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [],
},
{
@@ -997,6 +1080,7 @@ export const data: Array<UmbMockDataTypeModel> = [
hasChildren: false,
isFolder: false,
isDeletable: true,
canIgnoreStartNodes: false,
values: [],
},
];

View File

@@ -34,6 +34,7 @@ const createFolderMockMapper = (request: CreateFolderRequestModel): UmbMockDataT
hasChildren: false,
editorAlias: '',
isDeletable: true,
canIgnoreStartNodes: false,
values: [],
};
};
@@ -49,6 +50,7 @@ const createDetailMockMapper = (request: CreateDataTypeRequestModel): UmbMockDat
isFolder: false,
hasChildren: false,
isDeletable: true,
canIgnoreStartNodes: false,
};
};
@@ -61,6 +63,7 @@ const detailResponseMapper = (item: UmbMockDataTypeModel): DataTypeResponseModel
editorUiAlias: item.editorUiAlias,
values: item.values,
isDeletable: item.isDeletable,
canIgnoreStartNodes: item.canIgnoreStartNodes,
};
};

View File

@@ -57,6 +57,26 @@ export const data: Array<UmbMockMediaTypeModel> = [
labelOnTop: false,
},
},
{
id: '7',
container: { id: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75' },
alias: 'listView',
name: 'List View',
description: '',
dataType: { id: 'dt-collectionView' },
variesByCulture: false,
variesBySegment: false,
sortOrder: 2,
validation: {
mandatory: false,
mandatoryMessage: null,
regEx: null,
regExMessage: null,
},
appearance: {
labelOnTop: false,
},
},
],
containers: [
{
@@ -71,9 +91,12 @@ export const data: Array<UmbMockMediaTypeModel> = [
variesByCulture: false,
variesBySegment: false,
isElement: false,
allowedMediaTypes: [],
allowedMediaTypes: [
{ mediaType: { id: 'media-type-1-id' }, sortOrder: 0 },
],
compositions: [],
isFolder: false,
hasChildren: false,
collection: { id: 'dt-collectionView' },
},
];

View File

@@ -65,6 +65,7 @@ const createMockMediaTypeFolderMapper = (request: CreateFolderRequestModel): Umb
compositions: [],
isFolder: true,
hasChildren: false,
collection: null,
};
};
@@ -86,6 +87,7 @@ const createMockMediaTypeMapper = (request: CreateMediaTypeRequestModel): UmbMoc
parent: request.folder ? { id: request.folder.id } : null,
isFolder: false,
hasChildren: false,
collection: null,
};
};
@@ -104,6 +106,7 @@ const mediaTypeDetailMapper = (item: UmbMockMediaTypeModel): MediaTypeResponseMo
isElement: item.isElement,
allowedMediaTypes: item.allowedMediaTypes,
compositions: item.compositions,
collection: item.collection,
};
};

View File

@@ -74,6 +74,7 @@ export const data: Array<UmbMockMediaModel> = [
mediaType: {
id: 'media-type-1-id',
icon: 'icon-bug',
collection: { id: 'dt-collectionView' },
},
values: [],
variants: [
@@ -97,6 +98,7 @@ export const data: Array<UmbMockMediaModel> = [
mediaType: {
id: 'media-type-1-id',
icon: 'icon-bug',
collection: { id: 'dt-collectionView' },
},
values: [],
variants: [

View File

@@ -1,4 +1,4 @@
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbBaseController } from '@umbraco-cms/backoffice/class-api';
import { type UmbApi, UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
@@ -6,7 +6,7 @@ import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registr
export class UmbActionBase<RepositoryType> extends UmbBaseController implements UmbApi {
repository?: RepositoryType;
constructor(host: UmbControllerHostElement, repositoryAlias: string) {
constructor(host: UmbControllerHost, repositoryAlias: string) {
super(host);
new UmbExtensionApiInitializer(this, umbExtensionsRegistry, repositoryAlias, [this._host], (permitted, ctrl) => {

View File

@@ -14,3 +14,4 @@ export { UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION } from './collection-bu
export { UmbCollectionActionElement, UmbCollectionActionBase } from './action/index.js';
export type { UmbCollectionDataSource, UmbCollectionRepository } from './repository/index.js';
export type { UmbCollectionBulkActionPermissions, UmbCollectionConfiguration } from './types.js';

View File

@@ -1,20 +1,82 @@
import type { UmbAction} from '../action/index.js';
import type { UmbAction } from '../action/index.js';
import { UmbActionBase } from '../action/index.js';
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
/**
* Interface for an entity action.
* @export
* @interface UmbEntityAction<RepositoryType>
* @extends {UmbAction<RepositoryType>}
* @template RepositoryType
*/
export interface UmbEntityAction<RepositoryType> extends UmbAction<RepositoryType> {
/**
* The unique identifier of the entity.
* @type {string}
*/
unique: string;
/**
* The href location, the action will act as a link.
* @returns {Promise<string | null | undefined>}
*/
getHref(): Promise<string | null | undefined>;
/**
* The `execute` method, the action will act as a button.
* @returns {Promise<void>}
*/
execute(): Promise<void>;
}
export class UmbEntityActionBase<RepositoryType> extends UmbActionBase<RepositoryType> {
/**
* Base class for an entity action.
* @export
* @abstract
* @class UmbEntityActionBase<RepositoryType>
* @extends {UmbActionBase<RepositoryType>}
* @implements {UmbEntityAction<RepositoryType>}
* @template RepositoryType
*/
export abstract class UmbEntityActionBase<RepositoryType>
extends UmbActionBase<RepositoryType>
implements UmbEntityAction<RepositoryType>
{
entityType: string;
unique: string;
repositoryAlias: string;
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
/**
* Creates an instance of UmbEntityActionBase<RepositoryType>.
* @param {UmbControllerHost} host
* @param {string} repositoryAlias
* @param {string} unique
* @param {string} entityType
* @memberof UmbEntityActionBase<RepositoryType>
*/
constructor(host: UmbControllerHost, repositoryAlias: string, unique: string, entityType: string) {
super(host, repositoryAlias);
this.entityType = entityType;
this.unique = unique;
this.repositoryAlias = repositoryAlias;
}
/**
* By specifying the href, the action will act as a link.
* The `execute` method will not be called.
* @abstract
* @returns {string | null | undefined}
*/
public getHref(): Promise<string | null | undefined> {
return Promise.resolve(undefined);
}
/**
* By specifying the `execute` method, the action will act as a button.
* @abstract
* @returns {Promise<void>}
*/
public execute(): Promise<void> {
return Promise.resolve();
}
}

View File

@@ -5,6 +5,7 @@ import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controlle
export interface UmbEntityBulkAction<RepositoryType = unknown> extends UmbAction<RepositoryType> {
selection: Array<string>;
setSelection(selection: Array<string>): void;
execute(): Promise<void>;
}
export abstract class UmbEntityBulkActionBase<RepositoryType = unknown>

View File

@@ -0,0 +1,89 @@
import { umbExtensionsRegistry } from '../registry.js';
import { html, customElement, css } from '@umbraco-cms/backoffice/external/lit';
import { UMB_DEFAULT_COLLECTION_CONTEXT, UmbCollectionDefaultElement } from '@umbraco-cms/backoffice/collection';
import type { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection';
import type { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui';
@customElement('umb-extension-collection')
export class UmbExtensionCollectionElement extends UmbCollectionDefaultElement {
#collectionContext?: UmbDefaultCollectionContext;
#inputTimer?: NodeJS.Timeout;
#inputTimerAmount = 500;
#options: Array<Option> = [];
constructor() {
super();
this.consumeContext(UMB_DEFAULT_COLLECTION_CONTEXT, (collectionContext) => {
this.#collectionContext = collectionContext;
});
this.observe(umbExtensionsRegistry.extensions, (extensions) => {
const types = [...new Set(extensions.map((x) => x.type))];
const options = types.sort().map((x) => ({ name: this.#camelCaseToWords(x), value: x }));
this.#options = [{ name: 'All', value: '' }, ...options];
});
}
// credit: https://stackoverflow.com/a/7225450/12787 [LK]
#camelCaseToWords(input: string) {
const result = input.replace(/([A-Z])/g, ' $1');
return result.charAt(0).toUpperCase() + result.slice(1);
}
#onChange(event: UUISelectEvent) {
const extensionType = event.target.value;
console.log('onChange', extensionType);
this.#collectionContext?.setFilter({ type: extensionType });
}
#onSearch(event: InputEvent) {
const target = event.target as HTMLInputElement;
const query = target.value || '';
clearTimeout(this.#inputTimer);
this.#inputTimer = setTimeout(() => this.#collectionContext?.setFilter({ query }), this.#inputTimerAmount);
}
protected renderToolbar() {
return html`
<div id="toolbar" slot="header">
<uui-input @input=${this.#onSearch} label="Search" placeholder="Search..." id="input-search"></uui-input>
<uui-select
label="Select type..."
placeholder="Select type..."
.options=${this.#options}
@change=${this.#onChange}></uui-select>
</div>
`;
}
static styles = [
css`
#toolbar {
display: flex;
gap: var(--uui-size-space-5);
justify-content: space-between;
align-items: center;
padding-left: var(--uui-size-space-4);
padding-right: var(--uui-size-space-6);
width: 100%;
}
uui-input {
width: 100%;
}
uui-select {
width: 100%;
}
`,
];
}
export default UmbExtensionCollectionElement;
declare global {
interface HTMLElementTagNameMap {
'umb-extension-collection': UmbExtensionCollectionElement;
}
}

View File

@@ -10,6 +10,7 @@ const collectionManifest: ManifestCollection = {
kind: 'default',
alias: UMB_EXTENSION_COLLECTION_ALIAS,
name: 'Extension Collection',
element: () => import('./extension-collection.element.js'),
meta: {
repositoryAlias: UMB_EXTENSION_COLLECTION_REPOSITORY_ALIAS,
},

View File

@@ -5,6 +5,7 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbCollectionRepository } from '@umbraco-cms/backoffice/collection';
export interface UmbExtensionCollectionFilter {
query?: string;
skip: number;
take: number;
type?: ManifestTypes['type'];
@@ -17,9 +18,20 @@ export class UmbExtensionCollectionRepository extends UmbRepositoryBase implemen
async requestCollection(filter: UmbExtensionCollectionFilter) {
let extensions = umbExtensionsRegistry.getAllExtensions();
if (filter.query) {
const query = filter.query.toLowerCase();
extensions = extensions.filter(
(x) => x.name.toLowerCase().includes(query) || x.alias.toLowerCase().includes(query),
);
}
if (filter.type) {
extensions = extensions.filter((x) => x.type === filter.type);
}
extensions.sort((a, b) => a.type.localeCompare(b.type) || a.alias.localeCompare(b.alias));
const total = extensions.length;
const items = extensions.slice(filter.skip, filter.skip + filter.take);
const data = { items, total };

View File

@@ -20,6 +20,6 @@ export interface NumberRangeValueType {
max?: number;
}
export interface UmbReferenceById {
id: string;
export interface UmbReferenceByUnique {
unique: string;
}

View File

@@ -5,10 +5,13 @@ import type {
import type { UmbPropertyEditorConfigCollection } from '../../config/index.js';
import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import { UMB_DOCUMENT_COLLECTION_ALIAS } from '@umbraco-cms/backoffice/document';
import { UMB_MEDIA_COLLECTION_ALIAS } from '@umbraco-cms/backoffice/media';
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/document';
import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
import type { UmbDocumentWorkspaceContext } from '@umbraco-cms/backoffice/document';
import type { UmbMediaWorkspaceContext } from '@umbraco-cms/backoffice/media';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
/**
* @element umb-property-editor-ui-collection-view
@@ -18,6 +21,9 @@ export class UmbPropertyEditorUICollectionViewElement extends UmbLitElement impl
@property()
value?: string;
@state()
private _collectionAlias: string = UMB_DOCUMENT_COLLECTION_ALIAS;
@state()
private _config?: UmbCollectionConfiguration;
@@ -31,17 +37,25 @@ export class UmbPropertyEditorUICollectionViewElement extends UmbLitElement impl
// Gets the Data Type ID for the current property.
this.consumeContext(UMB_PROPERTY_CONTEXT, (propertyContext) => {
// TODO: [LK:2024-02-01] Replace `UMB_DOCUMENT_WORKSPACE_CONTEXT`
// with an abstracted context that supports both document and media workspaces.
this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (workspaceContext) => {
this.observe(workspaceContext.unique, (unique) => {
this.consumeContext(UMB_WORKSPACE_CONTEXT, (workspaceContext) => {
// TODO: [LK:2024-02-22] We need a solution that will allow the Collection property-editor
// to work in any workspace (that supports `unique` and `structure.getPropertyStructureByAlias`).
const entityType = workspaceContext.getEntityType();
const contentWorkspaceContext =
entityType === 'media'
? (workspaceContext as UmbMediaWorkspaceContext)
: (workspaceContext as UmbDocumentWorkspaceContext);
this._collectionAlias = entityType === 'media' ? UMB_MEDIA_COLLECTION_ALIAS : UMB_DOCUMENT_COLLECTION_ALIAS;
this.observe(contentWorkspaceContext.unique, (unique) => {
if (this._config) {
this._config.unique = unique;
}
});
this.observe(propertyContext.alias, async (propertyAlias) => {
if (propertyAlias) {
const property = await workspaceContext.structure.getPropertyStructureByAlias(propertyAlias);
const property = await contentWorkspaceContext.structure.getPropertyStructureByAlias(propertyAlias);
if (property && this._config) {
this._config.dataTypeId = property.dataType.unique;
this.requestUpdate('_config');
@@ -67,7 +81,7 @@ export class UmbPropertyEditorUICollectionViewElement extends UmbLitElement impl
render() {
if (!this._config?.unique || !this._config?.dataTypeId) return html`<uui-loader></uui-loader>`;
return html`<umb-collection alias="Umb.Collection.Document" .config=${this._config}></umb-collection>`;
return html`<umb-collection .alias=${this._collectionAlias} .config=${this._config}></umb-collection>`;
}
}

View File

@@ -19,9 +19,9 @@ export class UmbDocumentWorkspaceHasCollectionCondition extends UmbBaseControlle
this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (context) => {
this.observe(
context.contentTypeHasCollection,
(hasCollection) => {
this.permitted = hasCollection ?? false;
context.contentTypeCollection,
(collection) => {
this.permitted = !!collection?.unique;
this.#onChange();
},
'observeCollection',

View File

@@ -43,6 +43,7 @@ export class UmbDocumentServerDataSource implements UmbDetailDataSource<UmbDocum
template: null,
documentType: {
unique: '',
collection: null,
},
isTrashed: false,
values: [],
@@ -113,7 +114,7 @@ export class UmbDocumentServerDataSource implements UmbDetailDataSource<UmbDocum
template: data.template ? { unique: data.template.id } : null,
documentType: {
unique: data.documentType.id,
collection: data.documentType.collection ?? undefined,
collection: data.documentType.collection ? { unique: data.documentType.collection.id } : null,
},
isTrashed: data.isTrashed,
};

View File

@@ -38,7 +38,7 @@ const mapper = (item: DocumentItemResponseModel): UmbDocumentItemModel => {
documentType: {
unique: item.documentType.id,
icon: item.documentType.icon,
collection: item.documentType.collection ?? undefined,
collection: item.documentType.collection ? { unique: item.documentType.collection.id } : null,
},
variants: item.variants.map((variant) => {
return {

View File

@@ -1,5 +1,5 @@
import type { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbReferenceById } from '@umbraco-cms/backoffice/models';
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
export interface UmbDocumentItemModel {
name: string; // TODO: this is not correct. We need to get it from the variants. This is a temp solution.
@@ -9,7 +9,7 @@ export interface UmbDocumentItemModel {
documentType: {
unique: string;
icon: string;
collection?: UmbReferenceById;
collection: UmbReferenceByUnique | null;
};
variants: Array<UmbDocumentItemVariantModel>;
}

View File

@@ -55,7 +55,7 @@ const mapper = (item: DocumentTreeItemResponseModel): UmbDocumentTreeItemModel =
documentType: {
unique: item.documentType.id,
icon: item.documentType.icon,
collection: item.documentType.collection ?? undefined,
collection: item.documentType.collection ? { unique: item.documentType.collection.id } : null,
},
variants: item.variants.map((variant) => {
return {

View File

@@ -1,7 +1,7 @@
import type { UmbDocumentEntityType, UmbDocumentRootEntityType } from '../entity.js';
import type { UmbUniqueTreeItemModel, UmbUniqueTreeRootModel } from '@umbraco-cms/backoffice/tree';
import type { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbReferenceById } from '@umbraco-cms/backoffice/models';
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
export interface UmbDocumentTreeItemModel extends UmbUniqueTreeItemModel {
entityType: UmbDocumentEntityType;
@@ -11,7 +11,7 @@ export interface UmbDocumentTreeItemModel extends UmbUniqueTreeItemModel {
documentType: {
unique: string;
icon: string;
collection?: UmbReferenceById;
collection: UmbReferenceByUnique | null;
};
variants: Array<UmbDocumentTreeItemVariantModel>;
}

View File

@@ -1,13 +1,13 @@
import type { UmbDocumentEntityType } from './entity.js';
import type { UmbVariantModel } from '@umbraco-cms/backoffice/variant';
import type { UmbReferenceById } from '@umbraco-cms/backoffice/models';
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
import { DocumentVariantStateModel as UmbDocumentVariantState } from '@umbraco-cms/backoffice/external/backend-api';
export { UmbDocumentVariantState };
export interface UmbDocumentDetailModel {
documentType: {
unique: string;
collection?: UmbReferenceById;
collection: UmbReferenceByUnique | null;
};
entityType: UmbDocumentEntityType;
isTrashed: boolean;

View File

@@ -39,7 +39,7 @@ export class UmbDocumentWorkspaceContext
readonly unique = this.#currentData.asObservablePart((data) => data?.unique);
readonly contentTypeUnique = this.#currentData.asObservablePart((data) => data?.documentType.unique);
readonly contentTypeHasCollection = this.#currentData.asObservablePart((data) => !!data?.documentType.collection);
readonly contentTypeCollection = this.#currentData.asObservablePart((data) => data?.documentType.collection);
readonly variants = this.#currentData.asObservablePart((data) => data?.variants || []);
readonly urls = this.#currentData.asObservablePart((data) => data?.urls || []);
@@ -79,6 +79,7 @@ export class UmbDocumentWorkspaceContext
this.#getDataPromise = this.repository.createScaffold(parentUnique, {
documentType: {
unique: documentTypeUnique,
collection: null,
},
});
const { data } = await this.#getDataPromise;

View File

@@ -33,10 +33,9 @@ export class UmbDocumentWorkspaceViewCollectionElement extends UmbLitElement imp
this.observe(
workspaceContext.structure.ownerContentType(),
async (documentType) => {
if (!documentType) return;
if (!documentType || !documentType.collection) return;
// TODO: [LK] Temp hard-coded. Once the API is ready, wire up the data-type ID from the content-type.
const dataTypeUnique = 'c0808dd3-8133-4e4b-8ce8-e2bea84a96a4'; // documentType.collection.dataTypeId;
const dataTypeUnique = documentType.collection.unique;
if (dataTypeUnique) {
await this.#dataTypeDetailRepository.requestByUnique(dataTypeUnique);

View File

@@ -10,6 +10,13 @@ export class UmbMediaCollectionRepository implements UmbCollectionRepository {
this.#collectionSource = new UmbMediaCollectionServerDataSource(host);
}
async getDefaultConfiguration() {
return {
// TODO: The default Collection data-type ID (for the Media ListView) will come from the server soon. [LK]
defaultDataTypeId: '3a0156c4-3b8c-4803-bdc1-6871faa83fff',
};
}
async requestCollection(query: UmbMediaCollectionFilterModel) {
return this.#collectionSource.getCollection(query);
}

View File

@@ -13,9 +13,9 @@ export class UmbMediaCollectionServerDataSource implements UmbCollectionDataSour
}
async getCollection(query: UmbMediaCollectionFilterModel) {
// if (!query.dataTypeId) {
// throw new Error('Data type ID is required to fetch a collection.');
// }
if (!query.dataTypeId) {
throw new Error('Data type ID is required to fetch a collection.');
}
const params = {
id: query.unique ?? '',

View File

@@ -51,6 +51,9 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement {
@state()
private _selection: Array<string> = [];
@state()
private _skip: number = 0;
#collectionContext?: UmbDefaultCollectionContext<UmbMediaCollectionItemModel, UmbMediaCollectionFilterModel>;
constructor() {
@@ -89,6 +92,14 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement {
},
'umbCollectionSelectionObserver',
);
this.observe(
this.#collectionContext.pagination.skip,
(skip) => {
this._skip = skip;
},
'umbCollectionSkipObserver',
);
}
#createTableHeadings() {
@@ -113,14 +124,16 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement {
this.#createTableHeadings();
}
this._tableItems = items.map((item) => {
this._tableItems = items.map((item, rowIndex) => {
if (!item.unique) throw new Error('Item id is missing.');
const sortOrder = this._skip + rowIndex;
const data =
this._tableColumns?.map((column) => {
return {
columnAlias: column.alias,
value: column.elementName ? item : this.#getPropertyValueByAlias(item, column.alias),
value: column.elementName ? item : this.#getPropertyValueByAlias(sortOrder, item, column.alias),
};
}) ?? [];
@@ -132,7 +145,7 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement {
});
}
#getPropertyValueByAlias(item: UmbMediaCollectionItemModel, alias: string) {
#getPropertyValueByAlias(sortOrder: number, item: UmbMediaCollectionItemModel, alias: string) {
switch (alias) {
case 'createDate':
return item.createDate.toLocaleString();
@@ -140,6 +153,8 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement {
return item.name;
case 'owner':
return item.creator;
case 'sortOrder':
return sortOrder;
case 'updateDate':
return item.updateDate.toLocaleString();
default:

View File

@@ -0,0 +1 @@
export { UMB_MEDIA_WORKSPACE_HAS_COLLECTION_CONDITION } from './media-workspace-has-collection.condition.js';

View File

@@ -0,0 +1,3 @@
import { manifest as mediaWorkspaceHasCollectionCondition } from './media-workspace-has-collection.condition.js';
export const manifests = [mediaWorkspaceHasCollectionCondition];

View File

@@ -0,0 +1,44 @@
import { UMB_MEDIA_WORKSPACE_CONTEXT } from '../workspace/media-workspace.context-token.js';
import { UmbBaseController } from '@umbraco-cms/backoffice/class-api';
import type {
ManifestCondition,
UmbConditionConfigBase,
UmbConditionControllerArguments,
UmbExtensionCondition,
} from '@umbraco-cms/backoffice/extension-api';
export class UmbMediaWorkspaceHasCollectionCondition extends UmbBaseController implements UmbExtensionCondition {
config: MediaWorkspaceHasCollectionConditionConfig;
permitted = false;
#onChange: () => void;
constructor(args: UmbConditionControllerArguments<MediaWorkspaceHasCollectionConditionConfig>) {
super(args.host);
this.config = args.config;
this.#onChange = args.onChange;
this.consumeContext(UMB_MEDIA_WORKSPACE_CONTEXT, (context) => {
this.observe(
context.contentTypeCollection,
(collection) => {
this.permitted = !!collection?.unique;
this.#onChange();
},
'observeCollection',
);
});
}
}
export type MediaWorkspaceHasCollectionConditionConfig = UmbConditionConfigBase<
typeof UMB_MEDIA_WORKSPACE_HAS_COLLECTION_CONDITION
>;
export const UMB_MEDIA_WORKSPACE_HAS_COLLECTION_CONDITION = 'Umb.Condition.MediaWorkspaceHasCollection';
export const manifest: ManifestCondition = {
type: 'condition',
name: 'Media Workspace Has Collection Condition',
alias: UMB_MEDIA_WORKSPACE_HAS_COLLECTION_CONDITION,
api: UmbMediaWorkspaceHasCollectionCondition,
};

View File

@@ -7,6 +7,7 @@ export * from './tracked-reference/index.js';
export * from './user-permissions/index.js';
export * from './utils/index.js';
export * from './workspace/index.js';
export * from './conditions/index.js';
export { UMB_MEDIA_TREE_ALIAS } from './tree/index.js';
export { UMB_MEDIA_COLLECTION_ALIAS } from './collection/index.js';

View File

@@ -1,4 +1,5 @@
import { manifests as collectionManifests } from './collection/manifests.js';
import { manifests as conditionManifests } from './conditions/manifests.js';
import { manifests as entityActionsManifests } from './entity-actions/manifests.js';
import { manifests as entityBulkActionsManifests } from './entity-bulk-actions/manifests.js';
import { manifests as menuItemManifests } from './menu-item/manifests.js';
@@ -11,6 +12,7 @@ import { manifests as workspaceManifests } from './workspace/manifests.js';
export const manifests = [
...collectionManifests,
...conditionManifests,
...entityActionsManifests,
...entityBulkActionsManifests,
...menuItemManifests,

View File

@@ -40,6 +40,7 @@ export class UmbMediaServerDataSource implements UmbDetailDataSource<UmbMediaDet
urls: [],
mediaType: {
unique: mediaType.unique,
collection: mediaType.collection || null,
},
isTrashed: false,
values: [],
@@ -89,7 +90,10 @@ export class UmbMediaServerDataSource implements UmbDetailDataSource<UmbMediaDet
};
}),
urls: data.urls,
mediaType: { unique: data.mediaType.id },
mediaType: {
unique: data.mediaType.id,
collection: data.mediaType.collection ? { unique: data.mediaType.collection.id } : null,
},
isTrashed: data.isTrashed,
};

View File

@@ -37,7 +37,7 @@ const mapper = (item: MediaItemResponseModel): UmbMediaItemModel => {
mediaType: {
unique: item.mediaType.id,
icon: item.mediaType.icon,
collection: item.mediaType.collection ?? undefined,
collection: item.mediaType.collection ? { unique: item.mediaType.collection.id } : null,
},
variants: item.variants.map((variant) => {
return {

View File

@@ -1,4 +1,4 @@
import type { UmbReferenceById } from '@umbraco-cms/backoffice/models';
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
export interface UmbMediaItemModel {
unique: string;
@@ -6,7 +6,7 @@ export interface UmbMediaItemModel {
mediaType: {
unique: string;
icon: string;
collection?: UmbReferenceById;
collection: UmbReferenceByUnique | null;
};
variants: Array<UmbMediaItemVariantModel>;
name: string; // TODO: get correct variant name

View File

@@ -1,48 +1,80 @@
import { UMB_MEDIA_COLLECTION_ALIAS } from '../collection/index.js';
import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbMediaCollectionRepository } from '../collection/repository/index.js';
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbCollectionElement } from '@umbraco-cms/backoffice/collection';
import { UmbDataTypeDetailRepository } from '@umbraco-cms/backoffice/data-type';
import type {
UmbCollectionBulkActionPermissions,
UmbCollectionConfiguration,
} from '@umbraco-cms/backoffice/collection';
import type { UmbDataTypeDetailModel } from '@umbraco-cms/backoffice/data-type';
import type { UmbRoute } from '@umbraco-cms/backoffice/router';
import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
@customElement('umb-media-section-view')
export class UmbMediaSectionViewElement extends UmbLitElement {
#routes: UmbRoute[] = [
{
path: 'collection',
component: () => {
#dataTypeDetailRepository = new UmbDataTypeDetailRepository(this);
#mediaCollectionRepository = new UmbMediaCollectionRepository(this);
// TODO: [LK] Work-in-progress. Need to get the data-type configuration for the Media Collection.
const config = {
unique: '',
dataTypeId: '', //'3a0156c4-3b8c-4803-bdc1-6871faa83fff', //'dt-collectionView',
allowedEntityBulkActions: {
allowBulkCopy: true,
allowBulkDelete: true,
allowBulkMove: true,
allowBulkPublish: false,
allowBulkUnpublish: false,
@state()
private _routes?: UmbRoute[];
constructor() {
super();
this.#defineRoutes();
}
async #defineRoutes() {
const config = await this.#mediaCollectionRepository.getDefaultConfiguration();
await this.#dataTypeDetailRepository.requestByUnique(config.defaultDataTypeId);
this.observe(
await this.#dataTypeDetailRepository.byUnique(config.defaultDataTypeId),
(dataType) => {
if (!dataType) return;
const dataTypeConfig = this.#mapDataTypeConfigToCollectionConfig(dataType);
this._routes = [
{
path: 'collection',
component: () => {
const element = new UmbCollectionElement();
element.alias = UMB_MEDIA_COLLECTION_ALIAS;
element.config = dataTypeConfig;
return element;
},
},
orderBy: 'updateDate',
orderDirection: 'asc',
pageSize: 50,
useInfiniteEditor: false,
userDefinedProperties: undefined,
};
const element = new UmbCollectionElement();
element.alias = UMB_MEDIA_COLLECTION_ALIAS;
element.config = config;
return element;
{
path: '',
redirectTo: 'collection',
},
];
},
},
{
path: '',
redirectTo: 'collection',
},
];
'_observeConfigDataType',
);
}
#mapDataTypeConfigToCollectionConfig(dataType: UmbDataTypeDetailModel): UmbCollectionConfiguration {
const config = new UmbPropertyEditorConfigCollection(dataType.values);
return {
unique: '',
dataTypeId: dataType.unique,
allowedEntityBulkActions: config?.getValueByAlias<UmbCollectionBulkActionPermissions>('bulkActionPermissions'),
orderBy: config?.getValueByAlias('orderBy') ?? 'updateDate',
orderDirection: config?.getValueByAlias('orderDirection') ?? 'asc',
pageSize: Number(config?.getValueByAlias('pageSize')) ?? 50,
useInfiniteEditor: config?.getValueByAlias('useInfiniteEditor') ?? false,
userDefinedProperties: config?.getValueByAlias('includeProperties'),
};
}
render() {
return html`<umb-router-slot id="router-slot" .routes=${this.#routes}></umb-router-slot>`;
if (!this._routes) return;
return html`<umb-router-slot id="router-slot" .routes=${this._routes}></umb-router-slot>`;
}
static styles = [

View File

@@ -54,7 +54,7 @@ const mapper = (item: MediaTreeItemResponseModel): UmbMediaTreeItemModel => {
mediaType: {
unique: item.mediaType.id,
icon: item.mediaType.icon,
collection: item.mediaType.collection ?? undefined,
collection: item.mediaType.collection ? { unique: item.mediaType.collection.id } : null,
},
name: item.variants[0]?.name, // TODO: this is not correct. We need to get it from the variants. This is a temp solution.
variants: item.variants.map((variant) => {

View File

@@ -42,7 +42,7 @@ export class UmbMediaTreeStore extends UmbUniqueTreeStore {
hasChildren: false,
variants: item.variants,
isTrashed: item.isTrashed,
mediaType: { unique: item.mediaType.unique, icon: '' },
mediaType: { unique: item.mediaType.unique, icon: '', collection: null },
noAccess: false,
};

View File

@@ -1,5 +1,5 @@
import type { UmbMediaEntityType, UmbMediaRootEntityType } from '../entity.js';
import type { UmbReferenceById } from '@umbraco-cms/backoffice/models';
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
import type { UmbUniqueTreeItemModel, UmbUniqueTreeRootModel } from '@umbraco-cms/backoffice/tree';
export interface UmbMediaTreeItemModel extends UmbUniqueTreeItemModel {
@@ -9,7 +9,7 @@ export interface UmbMediaTreeItemModel extends UmbUniqueTreeItemModel {
mediaType: {
unique: string;
icon: string;
collection?: UmbReferenceById;
collection: UmbReferenceByUnique | null;
};
variants: Array<UmbMediaTreeItemVariantModel>;
}

View File

@@ -1,9 +1,13 @@
import type { UmbMediaEntityType } from './entity.js';
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
import type { UmbVariantModel } from '@umbraco-cms/backoffice/variant';
import type { MediaUrlInfoModel, MediaValueModel } from '@umbraco-cms/backoffice/external/backend-api';
export interface UmbMediaDetailModel {
mediaType: { unique: string };
mediaType: {
unique: string;
collection: UmbReferenceByUnique | null;
};
entityType: UmbMediaEntityType;
isTrashed: boolean;
unique: string;

View File

@@ -1,4 +1,4 @@
import { UMB_MEDIA_DETAIL_REPOSITORY_ALIAS } from '../repository/index.js';
import { UMB_MEDIA_WORKSPACE_HAS_COLLECTION_CONDITION } from '../conditions/media-workspace-has-collection.condition.js';
import { UmbSaveWorkspaceAction } from '@umbraco-cms/backoffice/workspace';
import type {
ManifestWorkspace,
@@ -18,24 +18,23 @@ const workspace: ManifestWorkspace = {
};
const workspaceViews: Array<ManifestWorkspaceView> = [
// {
// type: 'workspaceView',
// alias: 'Umb.WorkspaceView.Media.Collection',
// name: 'Media Workspace Collection View',
// element: () => import('./views/collection/media-collection-workspace-view.element.js'),
// weight: 300,
// meta: {
// label: 'Media',
// pathname: 'collection',
// icon: 'icon-grid',
// },
// conditions: [
// {
// alias: 'Umb.Condition.WorkspaceAlias',
// match: workspace.alias,
// },
// ],
// },
{
type: 'workspaceView',
alias: 'Umb.WorkspaceView.Media.Collection',
name: 'Media Workspace Collection View',
element: () => import('./views/collection/media-workspace-view-collection.element.js'),
weight: 300,
meta: {
label: 'Collection',
pathname: 'collection',
icon: 'icon-grid',
},
conditions: [
{
alias: UMB_MEDIA_WORKSPACE_HAS_COLLECTION_CONDITION,
},
],
},
{
type: 'workspaceView',
alias: 'Umb.WorkspaceView.Media.Edit',

View File

@@ -32,6 +32,7 @@ export class UmbMediaWorkspaceContext
readonly unique = this.#currentData.asObservablePart((data) => data?.unique);
readonly contentTypeUnique = this.#currentData.asObservablePart((data) => data?.mediaType.unique);
readonly contentTypeCollection = this.#currentData.asObservablePart((data) => data?.mediaType.collection);
readonly variants = this.#currentData.asObservablePart((data) => data?.variants || []);
readonly urls = this.#currentData.asObservablePart((data) => data?.urls || []);

View File

@@ -0,0 +1,83 @@
import { customElement, html, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbDataTypeDetailRepository } from '@umbraco-cms/backoffice/data-type';
import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
import { UMB_MEDIA_COLLECTION_ALIAS, UMB_MEDIA_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/media';
import type {
UmbCollectionBulkActionPermissions,
UmbCollectionConfiguration,
} from '@umbraco-cms/backoffice/collection';
import type { UmbDataTypeDetailModel } from '@umbraco-cms/backoffice/data-type';
import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry';
@customElement('umb-media-workspace-view-collection')
export class UmbMediaWorkspaceViewCollectionElement extends UmbLitElement implements UmbWorkspaceViewElement {
@state()
private _config?: UmbCollectionConfiguration;
@state()
private _mediaUnique?: string;
#dataTypeDetailRepository = new UmbDataTypeDetailRepository(this);
constructor() {
super();
this.#observeConfig();
}
async #observeConfig() {
this.consumeContext(UMB_MEDIA_WORKSPACE_CONTEXT, (workspaceContext) => {
this.observe(workspaceContext.unique, (unique) => {
this._mediaUnique = unique;
});
this.observe(
workspaceContext.structure.ownerContentType(),
async (mediaType) => {
if (!mediaType || !mediaType.collection) return;
const dataTypeUnique = mediaType.collection.unique;
if (dataTypeUnique) {
await this.#dataTypeDetailRepository.requestByUnique(dataTypeUnique);
this.observe(
await this.#dataTypeDetailRepository.byUnique(dataTypeUnique),
(dataType) => {
if (!dataType) return;
this._config = this.#mapDataTypeConfigToCollectionConfig(dataType);
},
'_observeConfigDataType',
);
}
},
'_observeConfigMediaType',
);
});
}
#mapDataTypeConfigToCollectionConfig(dataType: UmbDataTypeDetailModel): UmbCollectionConfiguration {
const config = new UmbPropertyEditorConfigCollection(dataType.values);
return {
unique: this._mediaUnique,
dataTypeId: dataType.unique,
allowedEntityBulkActions: config?.getValueByAlias<UmbCollectionBulkActionPermissions>('bulkActionPermissions'),
orderBy: config?.getValueByAlias('orderBy') ?? 'updateDate',
orderDirection: config?.getValueByAlias('orderDirection') ?? 'asc',
pageSize: Number(config?.getValueByAlias('pageSize')) ?? 50,
useInfiniteEditor: config?.getValueByAlias('useInfiniteEditor') ?? false,
userDefinedProperties: config?.getValueByAlias('includeProperties'),
};
}
render() {
if (!this._config?.unique || !this._config?.dataTypeId) return html`<uui-loader></uui-loader>`;
return html`<umb-collection .alias=${UMB_MEDIA_COLLECTION_ALIAS} .config=${this._config}></umb-collection>`;
}
}
export default UmbMediaWorkspaceViewCollectionElement;
declare global {
interface HTMLElementTagNameMap {
'umb-media-workspace-view-collection': UmbMediaWorkspaceViewCollectionElement;
}
}

View File

@@ -36,7 +36,7 @@ const mapper = (item: MemberItemResponseModel): UmbMemberItemModel => {
memberType: {
unique: item.memberType.id,
icon: item.memberType.icon,
collection: item.memberType.collection ?? undefined,
collection: item.memberType.collection ? { unique: item.memberType.collection.id } : null,
},
variants: item.variants.map((variant) => {
return {

View File

@@ -1,11 +1,11 @@
import type { UmbReferenceById } from '@umbraco-cms/backoffice/models';
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
export interface UmbMemberItemModel {
unique: string;
memberType: {
unique: string;
icon: string;
collection?: UmbReferenceById;
collection: UmbReferenceByUnique | null;
};
variants: Array<UmbMemberVariantItemModel>;
}