Merge pull request #2121 from umbraco/v14/feature/entity-bulk-action-move-to

Feature: Entity Bulk Action `moveTo` kind
This commit is contained in:
Lee Kelleher
2024-07-17 15:14:37 +01:00
committed by GitHub
24 changed files with 310 additions and 70 deletions

View File

@@ -0,0 +1 @@
export * from './move-to/index.js';

View File

@@ -0,0 +1,2 @@
export type { UmbBulkMoveToRepository } from './move-to-repository.interface.js';
export type { UmbBulkMoveToRequestArgs } from './types.js';

View File

@@ -0,0 +1,4 @@
import { manifest as moveToKindManifest } from './move-to.action.kind.js';
import type { ManifestTypes, UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [moveToKindManifest];

View File

@@ -0,0 +1,7 @@
import type { UmbRepositoryErrorResponse } from '../../../repository/types.js';
import type { UmbBulkMoveToRequestArgs } from './types.js';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
export interface UmbBulkMoveToRepository extends UmbApi {
requestBulkMoveTo(args: UmbBulkMoveToRequestArgs): Promise<UmbRepositoryErrorResponse>;
}

View File

@@ -0,0 +1,22 @@
import { UMB_ENTITY_BULK_ACTION_DEFAULT_KIND_MANIFEST } from '../../default/default.action.kind.js';
import type { UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifest: UmbBackofficeManifestKind = {
type: 'kind',
alias: 'Umb.Kind.EntityBulkAction.MoveTo',
matchKind: 'moveTo',
matchType: 'entityBulkAction',
manifest: {
...UMB_ENTITY_BULK_ACTION_DEFAULT_KIND_MANIFEST.manifest,
type: 'entityBulkAction',
kind: 'moveTo',
api: () => import('./move-to.action.js'),
weight: 700,
forEntityTypes: [],
meta: {
label: '#actions_move',
bulkMoveRepositoryAlias: '',
treeAlias: '',
},
},
};

View File

@@ -0,0 +1,63 @@
import type { UmbBulkMoveToRepository } from './move-to-repository.interface.js';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
import { UmbEntityBulkActionBase } from '@umbraco-cms/backoffice/entity-bulk-action';
import {
UmbRequestReloadChildrenOfEntityEvent,
UmbRequestReloadStructureForEntityEvent,
} from '@umbraco-cms/backoffice/entity-action';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { UMB_TREE_PICKER_MODAL } from '@umbraco-cms/backoffice/tree';
import type { MetaEntityBulkActionMoveToKind } from '@umbraco-cms/backoffice/extension-registry';
export class UmbMediaMoveEntityBulkAction extends UmbEntityBulkActionBase<MetaEntityBulkActionMoveToKind> {
async execute() {
if (this.selection?.length === 0) return;
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManager.open(this, UMB_TREE_PICKER_MODAL, {
data: {
foldersOnly: this.args.meta.foldersOnly,
hideTreeRoot: this.args.meta.hideTreeRoot,
treeAlias: this.args.meta.treeAlias,
},
});
const value = await modalContext.onSubmit().catch(() => undefined);
if (!value?.selection?.length) return;
const destinationUnique = value.selection[0];
if (destinationUnique === undefined) throw new Error('Destination Unique is not available');
const bulkMoveRepository = await createExtensionApiByAlias<UmbBulkMoveToRepository>(
this,
this.args.meta.bulkMoveRepositoryAlias,
);
if (!bulkMoveRepository) throw new Error('Bulk Move Repository is not available');
await bulkMoveRepository.requestBulkMoveTo({ uniques: this.selection, destination: { unique: destinationUnique } });
const entityContext = await this.getContext(UMB_ENTITY_CONTEXT);
if (!entityContext) throw new Error('Entity Context is not available');
const entityType = entityContext.getEntityType();
const unique = entityContext.getUnique();
if (entityType && unique !== undefined) {
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!eventContext) throw new Error('Event Context is not available');
const args = { entityType, unique };
const reloadChildren = new UmbRequestReloadChildrenOfEntityEvent(args);
eventContext.dispatchEvent(reloadChildren);
const reloadStructure = new UmbRequestReloadStructureForEntityEvent(args);
eventContext.dispatchEvent(reloadStructure);
}
}
}
export { UmbMediaMoveEntityBulkAction as api };

View File

@@ -0,0 +1,6 @@
export interface UmbBulkMoveToRequestArgs {
uniques: Array<string>;
destination: {
unique: string | null;
};
}

View File

@@ -0,0 +1,19 @@
import type { UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const UMB_ENTITY_BULK_ACTION_DEFAULT_KIND_MANIFEST: UmbBackofficeManifestKind = {
type: 'kind',
alias: 'Umb.Kind.EntityBulkAction.Default',
matchKind: 'default',
matchType: 'entityBulkAction',
manifest: {
type: 'entityBulkAction',
kind: 'default',
weight: 1000,
element: () => import('../entity-bulk-action.element.js'),
meta: {
label: 'Default Entity Bulk Action',
},
},
};
export const manifest = UMB_ENTITY_BULK_ACTION_DEFAULT_KIND_MANIFEST;

View File

@@ -0,0 +1,4 @@
import { manifest as defaultKindManifest } from './default.action.kind.js';
import type { ManifestTypes, UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [defaultKindManifest];

View File

@@ -0,0 +1,3 @@
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
export interface UmbEntityBulkActionElement extends UmbControllerHostElement {}

View File

@@ -1,16 +1,25 @@
import type { UmbEntityBulkActionBase } from './entity-bulk-action-base.js';
import { UmbActionExecutedEvent } from '@umbraco-cms/backoffice/event';
import type { UmbEntityBulkAction } from './entity-bulk-action.interface.js';
import type { UmbEntityBulkActionElement } from './entity-bulk-action-element.interface.js';
import { html, ifDefined, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import type { ManifestEntityBulkAction, MetaEntityBulkAction } from '@umbraco-cms/backoffice/extension-registry';
import { UmbActionExecutedEvent } from '@umbraco-cms/backoffice/event';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type {
ManifestEntityBulkAction,
MetaEntityBulkActionDefaultKind,
} from '@umbraco-cms/backoffice/extension-registry';
@customElement('umb-entity-bulk-action')
export class UmbEntityBulkActionElement<
MetaType extends MetaEntityBulkAction = MetaEntityBulkAction,
ApiType extends UmbEntityBulkActionBase<MetaType> = UmbEntityBulkActionBase<MetaType>,
> extends UmbLitElement {
const elementName = 'umb-entity-bulk-action';
@customElement(elementName)
export class UmbEntityBulkActionDefaultElement<
MetaType extends MetaEntityBulkActionDefaultKind = MetaEntityBulkActionDefaultKind,
ApiType extends UmbEntityBulkAction<MetaType> = UmbEntityBulkAction<MetaType>,
>
extends UmbLitElement
implements UmbEntityBulkActionElement
{
@property({ attribute: false })
manifest?: ManifestEntityBulkAction<MetaEntityBulkAction>;
manifest?: ManifestEntityBulkAction<MetaType>;
api?: ApiType;
@@ -22,16 +31,20 @@ export class UmbEntityBulkActionElement<
}
override render() {
return html`<uui-button
@click=${this.#onClick}
label=${ifDefined(this.manifest?.meta.label)}
color="default"
look="secondary"></uui-button>`;
return html`
<uui-button
color="default"
label=${ifDefined(this.localize.string(this.manifest?.meta.label ?? ''))}
look="secondary"
@click=${this.#onClick}></uui-button>
`;
}
}
export default UmbEntityBulkActionDefaultElement;
declare global {
interface HTMLElementTagNameMap {
'umb-entity-bulk-action': UmbEntityBulkActionElement;
[elementName]: UmbEntityBulkActionDefaultElement;
}
}

View File

@@ -1,4 +1,8 @@
export * from './types.js';
export * from './common/index.js';
export * from './entity-bulk-action-base.js';
export * from './entity-bulk-action.element.js';
export * from './entity-bulk-action.interface.js';
export type * from './entity-bulk-action-element.interface.js';
export { UMB_ENTITY_BULK_ACTION_DEFAULT_KIND_MANIFEST } from './default/default.action.kind.js';

View File

@@ -0,0 +1,8 @@
import { manifests as defaultEntityBulkActionManifests } from './default/manifests.js';
import { manifests as moveToEntityBulkActionManifests } from './common/move-to/manifests.js';
import type { ManifestTypes, UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [
...defaultEntityBulkActionManifests,
...moveToEntityBulkActionManifests,
];

View File

@@ -1,6 +1,6 @@
import type { ConditionTypes } from '../conditions/types.js';
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import type { UmbEntityBulkActionBase } from '@umbraco-cms/backoffice/entity-bulk-action';
import type { UmbEntityBulkActionElement } from '../../entity-bulk-action/entity-bulk-action-element.interface.js';
import type { UmbEntityBulkAction } from '@umbraco-cms/backoffice/entity-bulk-action';
import type { ManifestElementAndApi, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api';
/**
@@ -8,14 +8,21 @@ import type { ManifestElementAndApi, ManifestWithDynamicConditions } from '@umbr
* For example for content you may wish to move one or more documents in bulk
*/
export interface ManifestEntityBulkAction<MetaType extends MetaEntityBulkAction = MetaEntityBulkAction>
extends ManifestElementAndApi<UmbControllerHostElement, UmbEntityBulkActionBase<MetaType>>,
extends ManifestElementAndApi<UmbEntityBulkActionElement, UmbEntityBulkAction<MetaType>>,
ManifestWithDynamicConditions<ConditionTypes> {
type: 'entityBulkAction';
forEntityTypes: Array<string>;
meta: MetaType;
}
export interface MetaEntityBulkAction {
export interface MetaEntityBulkAction {}
export interface ManifestEntityBulkActionDefaultKind extends ManifestEntityBulkAction<MetaEntityBulkActionDefaultKind> {
type: 'entityBulkAction';
kind: 'default';
}
export interface MetaEntityBulkActionDefaultKind extends MetaEntityBulkAction {
/**
* The friendly name of the action to perform
*
@@ -26,3 +33,16 @@ export interface MetaEntityBulkAction {
*/
label?: string;
}
// MOVE TO
export interface ManifestEntityBulkActionMoveToKind extends ManifestEntityBulkAction<MetaEntityBulkActionMoveToKind> {
type: 'entityBulkAction';
kind: 'moveTo';
}
export interface MetaEntityBulkActionMoveToKind extends MetaEntityBulkActionDefaultKind {
bulkMoveRepositoryAlias: string;
hideTreeRoot?: boolean;
foldersOnly?: boolean;
treeAlias: string;
}

View File

@@ -5,6 +5,7 @@ import { manifests as contentTypeManifests } from './content-type/manifests.js';
import { manifests as cultureManifests } from './culture/manifests.js';
import { manifests as debugManifests } from './debug/manifests.js';
import { manifests as entityActionManifests } from './entity-action/manifests.js';
import { manifests as entityBulkActionManifests } from './entity-bulk-action/manifests.js';
import { manifests as extensionManifests } from './extension-registry/manifests.js';
import { manifests as iconRegistryManifests } from './icon-registry/manifests.js';
import { manifests as localizationManifests } from './localization/manifests.js';
@@ -38,6 +39,7 @@ export const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [
...settingsManifests,
...modalManifests,
...entityActionManifests,
...entityBulkActionManifests,
...propertyActionManifests,
...serverFileSystemManifests,
...debugManifests,

View File

@@ -1,18 +1,21 @@
import { UMB_MEDIA_COLLECTION_ALIAS } from '../collection/index.js';
import type { UmbCollectionBulkActionPermissions } from '@umbraco-cms/backoffice/collection';
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
import { UMB_MEDIA_ENTITY_TYPE } from '../entity.js';
import { manifests as moveToManifests } from './move-to/manifests.js';
import {
UMB_COLLECTION_ALIAS_CONDITION,
UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION,
} from '@umbraco-cms/backoffice/collection';
import type { ManifestEntityBulkAction, ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbCollectionBulkActionPermissions } from '@umbraco-cms/backoffice/collection';
export const manifests: Array<ManifestTypes> = [
const entityBulkActions: Array<ManifestEntityBulkAction> = [
{
type: 'entityBulkAction',
alias: 'Umb.EntityBulkAction.Media.Duplicate',
name: 'Duplicate Media Entity Bulk Action',
weight: 30,
api: () => import('./duplicate/duplicate.action.js'),
forEntityTypes: [UMB_MEDIA_ENTITY_TYPE],
meta: {
label: 'Duplicate',
},
@@ -27,32 +30,13 @@ export const manifests: Array<ManifestTypes> = [
},
],
},
{
type: 'entityBulkAction',
alias: 'Umb.EntityBulkAction.Media.MoveTo',
name: 'Move Media Entity Bulk Action',
weight: 20,
api: () => import('./move/move.action.js'),
meta: {
label: 'Move',
},
conditions: [
{
alias: UMB_COLLECTION_ALIAS_CONDITION,
match: UMB_MEDIA_COLLECTION_ALIAS,
},
{
alias: UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION,
match: (permissions: UmbCollectionBulkActionPermissions) => permissions.allowBulkMove,
},
],
},
{
type: 'entityBulkAction',
alias: 'Umb.EntityBulkAction.Media.Delete',
name: 'Delete Media Entity Bulk Action',
weight: 10,
api: () => import('./delete/delete.action.js'),
forEntityTypes: [UMB_MEDIA_ENTITY_TYPE],
meta: {
label: 'Delete',
},
@@ -68,3 +52,5 @@ export const manifests: Array<ManifestTypes> = [
],
},
];
export const manifests: Array<ManifestTypes> = [...entityBulkActions, ...moveToManifests];

View File

@@ -0,0 +1 @@
export { UmbBulkMoveToMediaRepository, UMB_BULK_MOVE_MEDIA_REPOSITORY_ALIAS } from './repository/index.js';

View File

@@ -0,0 +1,36 @@
import { UMB_MEDIA_COLLECTION_ALIAS } from '../../collection/index.js';
import { UMB_MEDIA_ENTITY_TYPE } from '../../entity.js';
import { UMB_MEDIA_TREE_ALIAS } from '../../tree/constants.js';
import { UMB_BULK_MOVE_MEDIA_REPOSITORY_ALIAS } from './repository/constants.js';
import { manifests as repositoryManifests } from './repository/manifests.js';
import {
UMB_COLLECTION_ALIAS_CONDITION,
UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION,
} from '@umbraco-cms/backoffice/collection';
import type { UmbCollectionBulkActionPermissions } from '@umbraco-cms/backoffice/collection';
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
const bulkMoveAction: ManifestTypes = {
type: 'entityBulkAction',
kind: 'moveTo',
alias: 'Umb.EntityBulkAction.Media.MoveTo',
name: 'Move Media Entity Bulk Action',
weight: 20,
forEntityTypes: [UMB_MEDIA_ENTITY_TYPE],
meta: {
bulkMoveRepositoryAlias: UMB_BULK_MOVE_MEDIA_REPOSITORY_ALIAS,
treeAlias: UMB_MEDIA_TREE_ALIAS,
},
conditions: [
{
alias: UMB_COLLECTION_ALIAS_CONDITION,
match: UMB_MEDIA_COLLECTION_ALIAS,
},
{
alias: UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION,
match: (permissions: UmbCollectionBulkActionPermissions) => permissions.allowBulkMove,
},
],
};
export const manifests: Array<ManifestTypes> = [bulkMoveAction, ...repositoryManifests];

View File

@@ -0,0 +1 @@
export const UMB_BULK_MOVE_MEDIA_REPOSITORY_ALIAS = 'Umb.Repository.Media.BulkMove';

View File

@@ -0,0 +1,2 @@
export { UmbBulkMoveToMediaRepository } from './move-to.repository.js';
export { UMB_BULK_MOVE_MEDIA_REPOSITORY_ALIAS } from './constants.js';

View File

@@ -0,0 +1,11 @@
import { UMB_BULK_MOVE_MEDIA_REPOSITORY_ALIAS } from './constants.js';
import type { ManifestRepository, ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
const bulkMoveRepository: ManifestRepository = {
type: 'repository',
alias: UMB_BULK_MOVE_MEDIA_REPOSITORY_ALIAS,
name: 'Bulk Move Media Repository',
api: () => import('./move-to.repository.js'),
};
export const manifests: Array<ManifestTypes> = [bulkMoveRepository];

View File

@@ -0,0 +1,44 @@
import { UmbMoveMediaServerDataSource } from '../../../entity-actions/move-to/repository/media-move.server.data-source.js';
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification';
import type { UmbBulkMoveToRepository, UmbBulkMoveToRequestArgs } from '@umbraco-cms/backoffice/entity-bulk-action';
import type { UmbRepositoryErrorResponse } from '@umbraco-cms/backoffice/repository';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class UmbBulkMoveToMediaRepository extends UmbRepositoryBase implements UmbBulkMoveToRepository {
#moveSource = new UmbMoveMediaServerDataSource(this);
#notificationContext?: typeof UMB_NOTIFICATION_CONTEXT.TYPE;
constructor(host: UmbControllerHost) {
super(host);
this.consumeContext(UMB_NOTIFICATION_CONTEXT, (notificationContext) => {
this.#notificationContext = notificationContext;
});
}
async requestBulkMoveTo(args: UmbBulkMoveToRequestArgs): Promise<UmbRepositoryErrorResponse> {
let count = 0;
const destination = args.destination;
for (const unique of args.uniques) {
const { error } = await this.#moveSource.moveTo({ unique, destination });
if (error) {
const notification = { data: { message: error.message } };
this.#notificationContext?.peek('danger', notification);
} else {
count++;
}
}
if (count > 0) {
const notification = { data: { message: `Moved ${count} media ${count === 1 ? 'item' : 'items'}` } };
this.#notificationContext?.peek('positive', notification);
}
return {};
}
}
export { UmbBulkMoveToMediaRepository as api };

View File

@@ -1,25 +0,0 @@
import { UMB_MEDIA_TREE_PICKER_MODAL } from '../../tree/index.js';
import { UmbEntityBulkActionBase } from '@umbraco-cms/backoffice/entity-bulk-action';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
export class UmbMediaMoveEntityBulkAction extends UmbEntityBulkActionBase<object> {
async execute() {
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
// TODO: the picker should be single picker by default
const modalContext = modalManager.open(this, UMB_MEDIA_TREE_PICKER_MODAL, {
data: {
multiple: false,
},
value: {
selection: [],
},
});
if (modalContext) {
//const { selection } = await modalContext.onSubmit();
//const destination = selection[0];
//await this.repository?.move(this.selection, destination);
}
}
}
export { UmbMediaMoveEntityBulkAction as api };

View File

@@ -1,20 +1,23 @@
import { UMB_MEDIA_COLLECTION_ALIAS } from '../collection/index.js';
import { UmbMediaCollectionRepository } from '../collection/repository/index.js';
import { UMB_MEDIA_COLLECTION_ALIAS } from '../collection/index.js';
import { UMB_MEDIA_ENTITY_TYPE } from '../entity.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 { UmbEntityContext } from '@umbraco-cms/backoffice/entity';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
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 {
#dataTypeDetailRepository = new UmbDataTypeDetailRepository(this);
#entityContext = new UmbEntityContext(this);
#mediaCollectionRepository = new UmbMediaCollectionRepository(this);
@state()
@@ -24,6 +27,9 @@ export class UmbMediaSectionViewElement extends UmbLitElement {
super();
this.#defineRoutes();
this.#entityContext.setEntityType(UMB_MEDIA_ENTITY_TYPE);
this.#entityContext.setUnique(null);
}
async #defineRoutes() {