Merge branch 'feature/document-type-workspace-take-3' of https://github.com/umbraco/Umbraco.CMS.Backoffice into feature/document-type-workspace-take-3

This commit is contained in:
Niels Lyngsø
2023-04-20 15:35:38 +02:00
12 changed files with 185 additions and 4 deletions

View File

@@ -0,0 +1,5 @@
import { UmbRepositoryResponse } from './detail-repository.interface';
export interface UmbCopyRepository {
copy(unique: string, targetUnique: string): Promise<UmbRepositoryResponse<string>>;
}

View File

@@ -0,0 +1,5 @@
import type { DataSourceResponse } from '@umbraco-cms/backoffice/repository';
export interface UmbCopyDataSource {
copy(unique: string, targetUnique: string): Promise<DataSourceResponse<string>>;
}

View File

@@ -4,3 +4,4 @@ export * from './folder-data-source.interface';
export * from './tree-data-source.interface';
export * from './item-data-source.interface';
export * from './move-data-source.interface';
export * from './copy-data-source.interface';

View File

@@ -4,3 +4,4 @@ export * from './tree-repository.interface';
export * from './folder-repository.interface';
export * from './item-repository.interface';
export * from './move-repository.interface';
export * from './copy-repository.interface';

View File

@@ -0,0 +1,27 @@
import { UmbDataTypeRepository } from '../../repository/data-type.repository';
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN, UMB_DATA_TYPE_PICKER_MODAL } from '@umbraco-cms/backoffice/modal';
import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api';
// TODO: investigate what we need to make a generic copy action
export class UmbCopyDataTypeEntityAction extends UmbEntityActionBase<UmbDataTypeRepository> {
#modalContext?: UmbModalContext;
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
super(host, repositoryAlias, unique);
new UmbContextConsumerController(this.host, UMB_MODAL_CONTEXT_TOKEN, (instance) => {
this.#modalContext = instance;
});
}
async execute() {
if (!this.#modalContext) throw new Error('Modal context is not available');
if (!this.repository) throw new Error('Repository is not available');
const modalHandler = this.#modalContext?.open(UMB_DATA_TYPE_PICKER_MODAL);
const { selection } = await modalHandler.onSubmit();
await this.repository.copy(this.unique, selection[0]);
}
}

View File

@@ -0,0 +1,24 @@
import { DATA_TYPE_ENTITY_TYPE } from '../..';
import { DATA_TYPE_REPOSITORY_ALIAS } from '../../repository/manifests';
import { UmbCopyDataTypeEntityAction } from './copy.action';
import { ManifestTypes } from '@umbraco-cms/backoffice/extensions-registry';
const entityActions: Array<ManifestTypes> = [
{
type: 'entityAction',
alias: 'Umb.EntityAction.DataType.Copy',
name: 'Copy Data Type Entity Action',
weight: 900,
meta: {
icon: 'umb:documents',
label: 'Copy to...',
repositoryAlias: DATA_TYPE_REPOSITORY_ALIAS,
api: UmbCopyDataTypeEntityAction,
},
conditions: {
entityType: DATA_TYPE_ENTITY_TYPE,
},
},
];
export const manifests = [...entityActions];

View File

@@ -2,6 +2,8 @@ import { DATA_TYPE_ENTITY_TYPE } from '..';
import { DATA_TYPE_REPOSITORY_ALIAS } from '../repository/manifests';
import { manifests as createManifests } from './create/manifests';
import { manifests as moveManifests } from './move/manifests';
import { manifests as copyManifests } from './copy/manifests';
import {
UmbDeleteEntityAction,
UmbDeleteFolderEntityAction,
@@ -57,4 +59,4 @@ const entityActions: Array<ManifestEntityAction> = [
},
];
export const manifests = [...entityActions, ...createManifests, ...moveManifests];
export const manifests = [...entityActions, ...createManifests, ...moveManifests, ...copyManifests];

View File

@@ -6,12 +6,14 @@ import { UmbDataTypeTreeStore, UMB_DATA_TYPE_TREE_STORE_CONTEXT_TOKEN } from './
import { UmbDataTypeFolderServerDataSource } from './sources/data-type-folder.server.data';
import { UmbDataTypeItemServerDataSource } from './sources/data-type-item.server.data';
import { UMB_DATA_TYPE_ITEM_STORE_CONTEXT_TOKEN, UmbDataTypeItemStore } from './data-type-item.store';
import { UmbDataTypeCopyServerDataSource } from './sources/data-type-copy.server.data';
import type {
UmbTreeRepository,
UmbDetailRepository,
UmbItemRepository,
UmbFolderRepository,
UmbMoveRepository,
UmbCopyRepository,
} from '@umbraco-cms/backoffice/repository';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api';
@@ -25,14 +27,14 @@ import {
UpdateDataTypeRequestModel,
} from '@umbraco-cms/backoffice/backend-api';
import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification';
export class UmbDataTypeRepository
implements
UmbItemRepository<DataTypeItemResponseModel>,
UmbTreeRepository<FolderTreeItemResponseModel>,
UmbDetailRepository<CreateDataTypeRequestModel, UpdateDataTypeRequestModel, DataTypeResponseModel>,
UmbFolderRepository,
UmbMoveRepository
UmbMoveRepository,
UmbCopyRepository
{
#init: Promise<unknown>;
@@ -43,6 +45,7 @@ export class UmbDataTypeRepository
#folderSource: UmbDataTypeFolderServerDataSource;
#itemSource: UmbDataTypeItemServerDataSource;
#moveSource: UmbDataTypeMoveServerDataSource;
#copySource: UmbDataTypeCopyServerDataSource;
#detailStore?: UmbDataTypeStore;
#treeStore?: UmbDataTypeTreeStore;
@@ -59,6 +62,7 @@ export class UmbDataTypeRepository
this.#folderSource = new UmbDataTypeFolderServerDataSource(this.#host);
this.#itemSource = new UmbDataTypeItemServerDataSource(this.#host);
this.#moveSource = new UmbDataTypeMoveServerDataSource(this.#host);
this.#copySource = new UmbDataTypeCopyServerDataSource(this.#host);
this.#init = Promise.all([
new UmbContextConsumerController(this.#host, UMB_DATA_TYPE_STORE_CONTEXT_TOKEN, (instance) => {
@@ -304,6 +308,24 @@ export class UmbDataTypeRepository
return { error };
}
async copy(id: string, targetId: string) {
await this.#init;
const { data: dataTypeCopyId, error } = await this.#copySource.copy(id, targetId);
if (error) return { error };
if (dataTypeCopyId) {
const { data: dataTypeCopy } = await this.requestById(dataTypeCopyId);
if (!dataTypeCopy) throw new Error('Could not find copied data type');
this.#treeStore?.appendItems([dataTypeCopy]);
this.#treeStore?.updateItem(targetId, { hasChildren: true });
const notification = { data: { message: `Data type copied` } };
this.#notificationContext?.peek('positive', notification);
}
return { data: dataTypeCopyId };
}
}
export const createTreeItem = (item: CreateDataTypeRequestModel): FolderTreeItemResponseModel => {

View File

@@ -0,0 +1,43 @@
import { DataTypeResource } from '@umbraco-cms/backoffice/backend-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
import { UmbCopyDataSource } from '@umbraco-cms/backoffice/repository';
/**
* A data source for Data Type items that fetches data from the server
* @export
* @class UmbDataTypeCopyServerDataSource
*/
export class UmbDataTypeCopyServerDataSource implements UmbCopyDataSource {
#host: UmbControllerHostElement;
/**
* Creates an instance of UmbDataTypeCopyServerDataSource.
* @param {UmbControllerHostElement} host
* @memberof UmbDataTypeCopyServerDataSource
*/
constructor(host: UmbControllerHostElement) {
this.#host = host;
}
/**
* Copy an item for the given id to the target id
* @param {Array<string>} id
* @return {*}
* @memberof UmbDataTypeCopyServerDataSource
*/
async copy(id: string, targetId: string) {
if (!id) throw new Error('Id is missing');
if (!targetId) throw new Error('Target Id is missing');
return tryExecuteAndNotify(
this.#host,
DataTypeResource.postDataTypeByIdCopy({
id,
requestBody: {
targetId,
},
})
);
}
}

View File

@@ -1,3 +1,4 @@
import { v4 as uuid } from 'uuid';
import { UmbData } from './data';
import type { Entity } from '@umbraco-cms/backoffice/models';
@@ -60,6 +61,30 @@ export class UmbEntityData<T extends Entity> extends UmbData<T> {
this.updateData(destinationItem);
}
copy(ids: Array<string>, destinationKey: string) {
const destinationItem = this.getById(destinationKey);
if (!destinationItem) throw new Error(`Destination item with key ${destinationKey} not found`);
// TODO: Notice we don't add numbers to the 'copy' name.
const items = this.getByIds(ids);
const copyItems = items.map((item) => {
return {
...item,
name: item.name + ' Copy',
id: uuid(),
parentId: destinationKey,
};
});
copyItems.forEach((copyItem) => this.insert(copyItem));
const newIds = copyItems.map((item) => item.id);
destinationItem.hasChildren = true;
this.updateData(destinationItem);
return newIds;
}
trash(ids: Array<string>) {
const trashedItems: Array<T> = [];

View File

@@ -0,0 +1,18 @@
import { rest } from 'msw';
import { umbDataTypeData } from '../../data/data-type.data';
import { slug } from './slug';
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
export const copyHandlers = [
rest.post(umbracoPath(`${slug}/:id/copy`), async (req, res, ctx) => {
const id = req.params.id as string;
if (!id) return;
const data = await req.json();
if (!data) return;
const newIds = umbDataTypeData.copy([id], data.targetId);
return res(ctx.status(200), ctx.set({ Location: newIds[0] }));
}),
];

View File

@@ -3,5 +3,13 @@ import { treeHandlers } from './tree.handlers';
import { detailHandlers } from './detail.handlers';
import { itemHandlers } from './item.handlers';
import { moveHandlers } from './move.handlers';
import { copyHandlers } from './copy.handlers';
export const handlers = [...treeHandlers, ...itemHandlers, ...folderHandlers, ...moveHandlers, ...detailHandlers];
export const handlers = [
...treeHandlers,
...itemHandlers,
...folderHandlers,
...moveHandlers,
...copyHandlers,
...detailHandlers,
];