Merge pull request #18154 from umbraco/v15/feature/enable-document-rollback-as-entity-action
Rollback as entity action + Picker data updates
This commit is contained in:
@@ -42,6 +42,7 @@ import type { UmbModalToken } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import {
|
||||
UmbEntityUpdatedEvent,
|
||||
UmbRequestReloadChildrenOfEntityEvent,
|
||||
UmbRequestReloadStructureForEntityEvent,
|
||||
} from '@umbraco-cms/backoffice/entity-action';
|
||||
@@ -709,13 +710,21 @@ export abstract class UmbContentDetailWorkspaceContextBase<
|
||||
);
|
||||
this._data.setCurrent(newCurrentData);
|
||||
|
||||
const unique = this.getUnique()!;
|
||||
const entityType = this.getEntityType();
|
||||
|
||||
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbRequestReloadStructureForEntityEvent({
|
||||
entityType: this.getEntityType(),
|
||||
unique: this.getUnique()!,
|
||||
const structureEvent = new UmbRequestReloadStructureForEntityEvent({ unique, entityType });
|
||||
eventContext.dispatchEvent(structureEvent);
|
||||
|
||||
const updatedEvent = new UmbEntityUpdatedEvent({
|
||||
unique,
|
||||
entityType,
|
||||
eventUnique: this._workspaceEventUnique,
|
||||
});
|
||||
|
||||
eventContext.dispatchEvent(event);
|
||||
eventContext.dispatchEvent(updatedEvent);
|
||||
|
||||
this._closeModal();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@ import { UmbControllerEvent } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface UmbEntityActionEventArgs extends UmbEntityModel {}
|
||||
export interface UmbEntityActionEventArgs extends UmbEntityModel {
|
||||
eventUnique?: string;
|
||||
}
|
||||
|
||||
export class UmbEntityActionEvent<
|
||||
ArgsType extends UmbEntityActionEventArgs = UmbEntityActionEventArgs,
|
||||
@@ -21,4 +23,8 @@ export class UmbEntityActionEvent<
|
||||
getUnique(): string | null {
|
||||
return this._args.unique;
|
||||
}
|
||||
|
||||
getEventUnique(): string | undefined {
|
||||
return this._args.eventUnique;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { UmbEntityActionEventArgs } from './entity-action.event.js';
|
||||
import { UmbEntityActionEvent } from './entity-action.event.js';
|
||||
|
||||
export class UmbEntityUpdatedEvent extends UmbEntityActionEvent {
|
||||
static readonly TYPE = 'entity-updated';
|
||||
|
||||
constructor(args: UmbEntityActionEventArgs) {
|
||||
super(UmbEntityUpdatedEvent.TYPE, args);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ export * from './constants.js';
|
||||
export * from './entity-action-base.js';
|
||||
export * from './entity-action-list.element.js';
|
||||
export * from './entity-action.event.js';
|
||||
export * from './entity-updated.event.js';
|
||||
export type * from './types.js';
|
||||
|
||||
export { UmbRequestReloadStructureForEntityEvent } from './request-reload-structure-for-entity.event.js';
|
||||
|
||||
@@ -4,6 +4,8 @@ import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { type ManifestRepository, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UmbEntityUpdatedEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
|
||||
const ObserveRepositoryAlias = Symbol();
|
||||
|
||||
@@ -14,6 +16,7 @@ export class UmbRepositoryItemsManager<ItemType extends { unique: string }> exte
|
||||
|
||||
#init: Promise<unknown>;
|
||||
#currentRequest?: Promise<unknown>;
|
||||
#eventContext?: typeof UMB_ACTION_EVENT_CONTEXT.TYPE;
|
||||
|
||||
// the init promise is used externally for recognizing when the manager is ready.
|
||||
public get init() {
|
||||
@@ -70,6 +73,20 @@ export class UmbRepositoryItemsManager<ItemType extends { unique: string }> exte
|
||||
},
|
||||
null,
|
||||
);
|
||||
|
||||
this.consumeContext(UMB_ACTION_EVENT_CONTEXT, (context) => {
|
||||
this.#eventContext = context;
|
||||
|
||||
this.#eventContext.removeEventListener(
|
||||
UmbEntityUpdatedEvent.TYPE,
|
||||
this.#onEntityUpdatedEvent as unknown as EventListener,
|
||||
);
|
||||
|
||||
this.#eventContext.addEventListener(
|
||||
UmbEntityUpdatedEvent.TYPE,
|
||||
this.#onEntityUpdatedEvent as unknown as EventListener,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getUniques(): Array<string> {
|
||||
@@ -122,6 +139,25 @@ export class UmbRepositoryItemsManager<ItemType extends { unique: string }> exte
|
||||
}
|
||||
}
|
||||
|
||||
async #reloadItem(unique: string): Promise<void> {
|
||||
await this.#init;
|
||||
if (!this.repository) throw new Error('Repository is not initialized');
|
||||
|
||||
const { data } = await this.repository.requestItems([unique]);
|
||||
|
||||
if (data) {
|
||||
const items = this.getItems();
|
||||
const item = items.find((item) => this.#getUnique(item) === unique);
|
||||
|
||||
if (item) {
|
||||
const index = items.indexOf(item);
|
||||
const newItems = [...items];
|
||||
newItems[index] = data[0];
|
||||
this.#items.setValue(this.#sortByUniques(newItems));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#sortByUniques(data: Array<ItemType>): Array<ItemType> {
|
||||
const uniques = this.getUniques();
|
||||
return [...data].sort((a, b) => {
|
||||
@@ -130,4 +166,25 @@ export class UmbRepositoryItemsManager<ItemType extends { unique: string }> exte
|
||||
return aIndex - bIndex;
|
||||
});
|
||||
}
|
||||
|
||||
#onEntityUpdatedEvent = (event: UmbEntityUpdatedEvent) => {
|
||||
const eventUnique = event.getUnique();
|
||||
|
||||
const items = this.getItems();
|
||||
if (items.length === 0) return;
|
||||
|
||||
// Ignore events if the entity is not in the list of items.
|
||||
const item = items.find((item) => this.#getUnique(item) === eventUnique);
|
||||
if (!item) return;
|
||||
|
||||
this.#reloadItem(item.unique);
|
||||
};
|
||||
|
||||
override destroy(): void {
|
||||
this.#eventContext?.removeEventListener(
|
||||
UmbEntityUpdatedEvent.TYPE,
|
||||
this.#onEntityUpdatedEvent as unknown as EventListener,
|
||||
);
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { UmbEntityContext, type UmbEntityModel, type UmbEntityUnique } from '@um
|
||||
import { UMB_DISCARD_CHANGES_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import {
|
||||
UmbEntityUpdatedEvent,
|
||||
UmbRequestReloadChildrenOfEntityEvent,
|
||||
UmbRequestReloadStructureForEntityEvent,
|
||||
} from '@umbraco-cms/backoffice/entity-action';
|
||||
@@ -15,6 +16,7 @@ import { umbExtensionsRegistry, type ManifestRepository } from '@umbraco-cms/bac
|
||||
import type { UmbDetailRepository } from '@umbraco-cms/backoffice/repository';
|
||||
import { UmbStateManager } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbValidationContext } from '@umbraco-cms/backoffice/validation';
|
||||
import { UmbId } from '@umbraco-cms/backoffice/id';
|
||||
|
||||
const LOADING_STATE_UNIQUE = 'umbLoadingEntityDetail';
|
||||
|
||||
@@ -45,6 +47,8 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
|
||||
protected _getDataPromise?: Promise<any>;
|
||||
protected _detailRepository?: DetailRepositoryType;
|
||||
|
||||
#eventContext?: typeof UMB_ACTION_EVENT_CONTEXT.TYPE;
|
||||
|
||||
#parent = new UmbObjectState<{ entityType: string; unique: UmbEntityUnique } | undefined>(undefined);
|
||||
public readonly parentUnique = this.#parent.asObservablePart((parent) => (parent ? parent.unique : undefined));
|
||||
public readonly parentEntityType = this.#parent.asObservablePart((parent) =>
|
||||
@@ -85,6 +89,19 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
|
||||
window.addEventListener('willchangestate', this.#onWillNavigate);
|
||||
this.#observeRepository(args.detailRepositoryAlias);
|
||||
this.addValidationContext(this.validationContext);
|
||||
|
||||
this.consumeContext(UMB_ACTION_EVENT_CONTEXT, (context) => {
|
||||
this.#eventContext = context;
|
||||
|
||||
this.#eventContext.removeEventListener(
|
||||
UmbEntityUpdatedEvent.TYPE,
|
||||
this.#onEntityUpdatedEvent as unknown as EventListener,
|
||||
);
|
||||
this.#eventContext.addEventListener(
|
||||
UmbEntityUpdatedEvent.TYPE,
|
||||
this.#onEntityUpdatedEvent as unknown as EventListener,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,13 +324,21 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
|
||||
this._data.setPersisted(data);
|
||||
this._data.setCurrent(data);
|
||||
|
||||
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbRequestReloadStructureForEntityEvent({
|
||||
unique: this.getUnique()!,
|
||||
entityType: this.getEntityType(),
|
||||
const unique = this.getUnique()!;
|
||||
const entityType = this.getEntityType();
|
||||
|
||||
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbRequestReloadStructureForEntityEvent({ unique, entityType });
|
||||
|
||||
eventContext.dispatchEvent(event);
|
||||
|
||||
const updatedEvent = new UmbEntityUpdatedEvent({
|
||||
unique,
|
||||
entityType,
|
||||
eventUnique: this._workspaceEventUnique,
|
||||
});
|
||||
|
||||
actionEventContext.dispatchEvent(event);
|
||||
eventContext.dispatchEvent(updatedEvent);
|
||||
}
|
||||
|
||||
#allowNavigateAway = false;
|
||||
@@ -396,8 +421,30 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
|
||||
}
|
||||
}
|
||||
|
||||
// Discriminator to identify events from this workspace context
|
||||
protected readonly _workspaceEventUnique = UmbId.new();
|
||||
|
||||
#onEntityUpdatedEvent = (event: UmbEntityUpdatedEvent) => {
|
||||
const eventEntityUnique = event.getUnique();
|
||||
const eventEntityType = event.getEntityType();
|
||||
const eventDiscriminator = event.getEventUnique();
|
||||
|
||||
// Ignore events for other entities
|
||||
if (eventEntityType !== this.getEntityType()) return;
|
||||
if (eventEntityUnique !== this.getUnique()) return;
|
||||
|
||||
// Ignore events from this workspace so we don't reload the data twice. Ex saving this workspace
|
||||
if (eventDiscriminator === this._workspaceEventUnique) return;
|
||||
|
||||
this.reload();
|
||||
};
|
||||
|
||||
public override destroy(): void {
|
||||
window.removeEventListener('willchangestate', this.#onWillNavigate);
|
||||
this.#eventContext?.removeEventListener(
|
||||
UmbEntityUpdatedEvent.TYPE,
|
||||
this.#onEntityUpdatedEvent as unknown as EventListener,
|
||||
);
|
||||
this._detailRepository?.destroy();
|
||||
this.#entityContext.destroy();
|
||||
super.destroy();
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
UMB_USER_PERMISSION_DOCUMENT_ROLLBACK,
|
||||
UMB_DOCUMENT_ENTITY_TYPE,
|
||||
UMB_DOCUMENT_WORKSPACE_ALIAS,
|
||||
} from '../../constants.js';
|
||||
import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
|
||||
import { UMB_USER_PERMISSION_DOCUMENT_ROLLBACK, UMB_DOCUMENT_ENTITY_TYPE } from '../../constants.js';
|
||||
import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin';
|
||||
|
||||
export const manifests: Array<UmbExtensionManifest> = [
|
||||
@@ -12,7 +7,7 @@ export const manifests: Array<UmbExtensionManifest> = [
|
||||
kind: 'default',
|
||||
alias: 'Umb.EntityAction.Document.Rollback',
|
||||
name: 'Rollback Document Entity Action',
|
||||
weight: 500,
|
||||
weight: 450,
|
||||
api: () => import('./rollback.action.js'),
|
||||
forEntityTypes: [UMB_DOCUMENT_ENTITY_TYPE],
|
||||
meta: {
|
||||
@@ -27,12 +22,6 @@ export const manifests: Array<UmbExtensionManifest> = [
|
||||
{
|
||||
alias: UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS,
|
||||
},
|
||||
/* Currently the rollback is tightly coupled to the workspace contexts so we only allow it to show up
|
||||
In the document workspace. */
|
||||
{
|
||||
alias: UMB_WORKSPACE_CONDITION_ALIAS,
|
||||
match: UMB_DOCUMENT_WORKSPACE_ALIAS,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { UMB_DOCUMENT_WORKSPACE_CONTEXT, UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN } from '../../constants.js';
|
||||
import { UMB_DOCUMENT_ENTITY_TYPE } from '../../constants.js';
|
||||
import { UmbRollbackRepository } from '../repository/rollback.repository.js';
|
||||
import { UmbDocumentDetailRepository } from '../../repository/index.js';
|
||||
import type { UmbDocumentDetailModel } from '../../types.js';
|
||||
import type { UmbRollbackModalData, UmbRollbackModalValue } from './types.js';
|
||||
import { diffWords, type Change } from '@umbraco-cms/backoffice/external/diff';
|
||||
import { css, customElement, html, nothing, repeat, state, unsafeHTML } from '@umbraco-cms/backoffice/external/lit';
|
||||
@@ -8,8 +10,13 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbUserItemRepository } from '@umbraco-cms/backoffice/user';
|
||||
import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
|
||||
import type { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UMB_APP_LANGUAGE_CONTEXT, UmbLanguageItemRepository } from '@umbraco-cms/backoffice/language';
|
||||
import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity';
|
||||
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
|
||||
import '../../modals/shared/document-variant-language-picker.element.js';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UmbEntityUpdatedEvent, UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
|
||||
type DocumentVersion = {
|
||||
id: string;
|
||||
@@ -22,10 +29,10 @@ type DocumentVersion = {
|
||||
@customElement('umb-rollback-modal')
|
||||
export class UmbRollbackModalElement extends UmbModalBaseElement<UmbRollbackModalData, UmbRollbackModalValue> {
|
||||
@state()
|
||||
versions: DocumentVersion[] = [];
|
||||
_versions: DocumentVersion[] = [];
|
||||
|
||||
@state()
|
||||
currentVersion?: {
|
||||
_selectedVersion?: {
|
||||
date: string;
|
||||
name: string;
|
||||
user: string;
|
||||
@@ -37,18 +44,20 @@ export class UmbRollbackModalElement extends UmbModalBaseElement<UmbRollbackModa
|
||||
};
|
||||
|
||||
@state()
|
||||
currentCulture?: string;
|
||||
_selectedCulture: string | null = null;
|
||||
|
||||
@state()
|
||||
availableVariants: Option[] = [];
|
||||
_isInvariant = true;
|
||||
|
||||
@state()
|
||||
_availableVariants: Option[] = [];
|
||||
|
||||
@state()
|
||||
_diffs: Array<{ alias: string; diff: Change[] }> = [];
|
||||
|
||||
#rollbackRepository = new UmbRollbackRepository(this);
|
||||
#userItemRepository = new UmbUserItemRepository(this);
|
||||
|
||||
#workspaceContext?: typeof UMB_DOCUMENT_WORKSPACE_CONTEXT.TYPE;
|
||||
|
||||
#propertyDatasetContext?: typeof UMB_PROPERTY_DATASET_CONTEXT.TYPE;
|
||||
|
||||
#localizeDateOptions: Intl.DateTimeFormatOptions = {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
@@ -56,37 +65,76 @@ export class UmbRollbackModalElement extends UmbModalBaseElement<UmbRollbackModa
|
||||
minute: '2-digit',
|
||||
};
|
||||
|
||||
#currentDocument: UmbDocumentDetailModel | undefined;
|
||||
#currentAppCulture: string | undefined;
|
||||
#currentDatasetCulture: string | undefined;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, (instance) => {
|
||||
this.#propertyDatasetContext = instance;
|
||||
this.currentCulture = instance.getVariantId().culture ?? undefined;
|
||||
this.#requestVersions();
|
||||
this.#currentDatasetCulture = instance.getVariantId().culture ?? undefined;
|
||||
this.#selectCulture();
|
||||
});
|
||||
|
||||
this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (instance) => {
|
||||
this.#workspaceContext = instance;
|
||||
this.consumeContext(UMB_APP_LANGUAGE_CONTEXT, (instance) => {
|
||||
this.#currentAppCulture = instance.getAppCulture();
|
||||
this.#selectCulture();
|
||||
});
|
||||
|
||||
this.observe(instance.variantOptions, (options) => {
|
||||
this.availableVariants = options.map((option) => {
|
||||
this.consumeContext(UMB_ENTITY_CONTEXT, async (instance) => {
|
||||
if (instance.getEntityType() !== UMB_DOCUMENT_ENTITY_TYPE) {
|
||||
throw new Error(`Entity type is not ${UMB_DOCUMENT_ENTITY_TYPE}`);
|
||||
}
|
||||
|
||||
const unique = instance?.getUnique();
|
||||
|
||||
if (!unique) {
|
||||
throw new Error('Document unique is not set');
|
||||
}
|
||||
|
||||
const { data } = await new UmbDocumentDetailRepository(this).requestByUnique(unique);
|
||||
if (!data) return;
|
||||
|
||||
this.#currentDocument = data;
|
||||
const itemVariants = this.#currentDocument?.variants ?? [];
|
||||
|
||||
this._isInvariant = itemVariants.length === 1 && new UmbVariantId(itemVariants[0].culture).isInvariant();
|
||||
this.#selectCulture();
|
||||
|
||||
const cultures = itemVariants.map((x) => x.culture).filter((x) => x !== null) as string[];
|
||||
const { data: languageItems } = await new UmbLanguageItemRepository(this).requestItems(cultures);
|
||||
|
||||
if (languageItems) {
|
||||
this._availableVariants = languageItems.map((language) => {
|
||||
return {
|
||||
name: option.language.name,
|
||||
value: option.language.unique,
|
||||
selected: option.language.unique === this.currentCulture,
|
||||
name: language.name,
|
||||
value: language.unique,
|
||||
selected: language.unique === this._selectedCulture,
|
||||
};
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this._availableVariants = [];
|
||||
}
|
||||
|
||||
this.#requestVersions();
|
||||
});
|
||||
}
|
||||
|
||||
#selectCulture() {
|
||||
const contextCulture = this.#currentDatasetCulture ?? this.#currentAppCulture ?? null;
|
||||
this._selectedCulture = this._isInvariant ? null : contextCulture;
|
||||
}
|
||||
|
||||
async #requestVersions() {
|
||||
if (!this.#propertyDatasetContext) return;
|
||||
if (!this.#currentDocument?.unique) {
|
||||
throw new Error('Document unique is not set');
|
||||
}
|
||||
|
||||
const documentId = this.#propertyDatasetContext.getUnique();
|
||||
if (!documentId) return;
|
||||
|
||||
const { data } = await this.#rollbackRepository.requestVersionsByDocumentId(documentId, this.currentCulture);
|
||||
const { data } = await this.#rollbackRepository.requestVersionsByDocumentId(
|
||||
this.#currentDocument?.unique,
|
||||
this._selectedCulture ?? undefined,
|
||||
);
|
||||
if (!data) return;
|
||||
|
||||
const tempItems: DocumentVersion[] = [];
|
||||
@@ -108,28 +156,38 @@ export class UmbRollbackModalElement extends UmbModalBaseElement<UmbRollbackModa
|
||||
});
|
||||
});
|
||||
|
||||
this.versions = tempItems;
|
||||
this._versions = tempItems;
|
||||
const id = tempItems.find((item) => item.isCurrentlyPublishedVersion)?.id;
|
||||
|
||||
if (id) {
|
||||
this.#setCurrentVersion(id);
|
||||
this.#selectVersion(id);
|
||||
}
|
||||
}
|
||||
|
||||
async #setCurrentVersion(id: string) {
|
||||
const version = this.versions.find((item) => item.id === id);
|
||||
if (!version) return;
|
||||
async #selectVersion(id: string) {
|
||||
const version = this._versions.find((item) => item.id === id);
|
||||
|
||||
if (!version) {
|
||||
this._selectedVersion = undefined;
|
||||
this._diffs = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const { data } = await this.#rollbackRepository.requestVersionById(id);
|
||||
if (!data) return;
|
||||
|
||||
this.currentVersion = {
|
||||
if (!data) {
|
||||
this._selectedVersion = undefined;
|
||||
this._diffs = [];
|
||||
return;
|
||||
}
|
||||
|
||||
this._selectedVersion = {
|
||||
date: version.date,
|
||||
user: version.user,
|
||||
name: data.variants.find((x) => x.culture === this.currentCulture)?.name || data.variants[0].name,
|
||||
name: data.variants.find((x) => x.culture === this._selectedCulture)?.name || data.variants[0].name,
|
||||
id: data.id,
|
||||
properties: data.values
|
||||
.filter((x) => x.culture === this.currentCulture || !x.culture) // When invariant, culture is undefined or null.
|
||||
.filter((x) => x.culture === this._selectedCulture || !x.culture) // When invariant, culture is undefined or null.
|
||||
.map((value: any) => {
|
||||
return {
|
||||
alias: value.alias,
|
||||
@@ -137,20 +195,35 @@ export class UmbRollbackModalElement extends UmbModalBaseElement<UmbRollbackModa
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
await this.#setDiffs();
|
||||
}
|
||||
|
||||
#onRollback() {
|
||||
if (!this.currentVersion) return;
|
||||
async #onRollback() {
|
||||
if (!this._selectedVersion) return;
|
||||
|
||||
const id = this.currentVersion.id;
|
||||
const culture = this.availableVariants.length > 1 ? this.currentCulture : undefined;
|
||||
this.#rollbackRepository.rollback(id, culture);
|
||||
const id = this._selectedVersion.id;
|
||||
const culture = this._selectedCulture ?? undefined;
|
||||
|
||||
const docUnique = this.#workspaceContext?.getUnique() ?? '';
|
||||
// TODO Use the load method on the context instead of location.href, when it works.
|
||||
// this.#workspaceContext?.load(docUnique);
|
||||
location.href = UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN.generateAbsolute({ unique: docUnique });
|
||||
this.modalContext?.reject();
|
||||
const { error } = await this.#rollbackRepository.rollback(id, culture);
|
||||
if (error) return;
|
||||
|
||||
const unique = this.#currentDocument?.unique;
|
||||
const entityType = this.#currentDocument?.entityType;
|
||||
|
||||
if (!unique || !entityType) {
|
||||
throw new Error('Document unique or entity type is not set');
|
||||
}
|
||||
|
||||
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
|
||||
const reloadStructureEvent = new UmbRequestReloadStructureForEntityEvent({ unique, entityType });
|
||||
actionEventContext.dispatchEvent(reloadStructureEvent);
|
||||
|
||||
const entityUpdatedEvent = new UmbEntityUpdatedEvent({ unique, entityType });
|
||||
actionEventContext.dispatchEvent(entityUpdatedEvent);
|
||||
|
||||
this.modalContext?.submit();
|
||||
}
|
||||
|
||||
#onCancel() {
|
||||
@@ -158,7 +231,7 @@ export class UmbRollbackModalElement extends UmbModalBaseElement<UmbRollbackModa
|
||||
}
|
||||
|
||||
#onVersionClicked(id: string) {
|
||||
this.#setCurrentVersion(id);
|
||||
this.#selectVersion(id);
|
||||
}
|
||||
|
||||
#onPreventCleanup(event: Event, id: string, preventCleanup: boolean) {
|
||||
@@ -166,7 +239,7 @@ export class UmbRollbackModalElement extends UmbModalBaseElement<UmbRollbackModa
|
||||
event.stopImmediatePropagation();
|
||||
this.#rollbackRepository.setPreventCleanup(id, preventCleanup);
|
||||
|
||||
const version = this.versions.find((item) => item.id === id);
|
||||
const version = this._versions.find((item) => item.id === id);
|
||||
if (!version) return;
|
||||
|
||||
version.preventCleanup = preventCleanup;
|
||||
@@ -176,125 +249,147 @@ export class UmbRollbackModalElement extends UmbModalBaseElement<UmbRollbackModa
|
||||
#onChangeCulture(event: UUISelectEvent) {
|
||||
const value = event.target.value;
|
||||
|
||||
this.currentCulture = value.toString();
|
||||
this._selectedCulture = value.toString();
|
||||
this.#requestVersions();
|
||||
}
|
||||
|
||||
#trimQuotes(str: string): string {
|
||||
return str.replace(/^['"]|['"]$/g, '');
|
||||
}
|
||||
|
||||
#renderCultureSelect() {
|
||||
if (this.availableVariants.length < 2) return nothing;
|
||||
return html`
|
||||
<div id="language-select">
|
||||
<b>${this.localize.term('general_language')}</b>
|
||||
<uui-select @change=${this.#onChangeCulture} .options=${this.availableVariants}></uui-select>
|
||||
</div>
|
||||
<uui-select
|
||||
id="language-select"
|
||||
@change=${this.#onChangeCulture}
|
||||
.options=${this._availableVariants}></uui-select>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderVersions() {
|
||||
return html` ${this.#renderCultureSelect()}
|
||||
${repeat(
|
||||
this.versions,
|
||||
(item) => item.id,
|
||||
(item) => {
|
||||
return html`
|
||||
<div
|
||||
@click=${() => this.#onVersionClicked(item.id)}
|
||||
@keydown=${() => {}}
|
||||
class="rollback-item ${this.currentVersion?.id === item.id ? 'active' : ''}">
|
||||
<div>
|
||||
<p class="rollback-item-date">
|
||||
<umb-localize-date date="${item.date}" .options=${this.#localizeDateOptions}></umb-localize-date>
|
||||
</p>
|
||||
<p>${item.user}</p>
|
||||
<p>${item.isCurrentlyPublishedVersion ? this.localize.term('rollback_currentPublishedVersion') : ''}</p>
|
||||
if (!this._versions.length) {
|
||||
return html`<uui-box headline=${this.localize.term('rollback_versions')}>No versions available</uui-box>`;
|
||||
}
|
||||
|
||||
return html` <uui-box id="versions-box" headline=${this.localize.term('rollback_versions')}>
|
||||
${repeat(
|
||||
this._versions,
|
||||
(item) => item.id,
|
||||
(item) => {
|
||||
return html`
|
||||
<div
|
||||
@click=${() => this.#onVersionClicked(item.id)}
|
||||
@keydown=${() => {}}
|
||||
class="rollback-item ${this._selectedVersion?.id === item.id ? 'active' : ''}">
|
||||
<div>
|
||||
<p class="rollback-item-date">
|
||||
<umb-localize-date date="${item.date}" .options=${this.#localizeDateOptions}></umb-localize-date>
|
||||
</p>
|
||||
<p>${item.user}</p>
|
||||
<p>${item.isCurrentlyPublishedVersion ? this.localize.term('rollback_currentPublishedVersion') : ''}</p>
|
||||
</div>
|
||||
<uui-button
|
||||
look="secondary"
|
||||
@click=${(event: Event) => this.#onPreventCleanup(event, item.id, !item.preventCleanup)}
|
||||
label=${item.preventCleanup
|
||||
? this.localize.term('contentTypeEditor_historyCleanupEnableCleanup')
|
||||
: this.localize.term('contentTypeEditor_historyCleanupPreventCleanup')}></uui-button>
|
||||
</div>
|
||||
<uui-button
|
||||
look="secondary"
|
||||
@click=${(event: Event) => this.#onPreventCleanup(event, item.id, !item.preventCleanup)}
|
||||
label=${item.preventCleanup
|
||||
? this.localize.term('contentTypeEditor_historyCleanupEnableCleanup')
|
||||
: this.localize.term('contentTypeEditor_historyCleanupPreventCleanup')}></uui-button>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
)}`;
|
||||
`;
|
||||
},
|
||||
)}</uui-box
|
||||
>`;
|
||||
}
|
||||
|
||||
#renderCurrentVersion() {
|
||||
if (!this.currentVersion) return;
|
||||
async #setDiffs() {
|
||||
if (!this._selectedVersion) return;
|
||||
|
||||
let draftValues =
|
||||
(this.#workspaceContext?.getData()?.values as Array<{ alias: string; culture: string; value: any }>) ?? [];
|
||||
const currentPropertyValues = this.#currentDocument?.values.filter(
|
||||
(x) => x.culture === this._selectedCulture || !x.culture,
|
||||
); // When invariant, culture is undefined or null.
|
||||
|
||||
draftValues = draftValues.filter((x) => x.culture === this.currentCulture || !x.culture); // When invariant, culture is undefined or null.
|
||||
if (!currentPropertyValues) {
|
||||
throw new Error('Current property values are not set');
|
||||
}
|
||||
|
||||
const currentName = this.#currentDocument?.variants.find((x) => x.culture === this._selectedCulture)?.name;
|
||||
|
||||
if (!currentName) {
|
||||
throw new Error('Current name is not set');
|
||||
}
|
||||
|
||||
const diffs: Array<{ alias: string; diff: Change[] }> = [];
|
||||
|
||||
const nameDiff = diffWords(this.#workspaceContext?.getName() ?? '', this.currentVersion.name);
|
||||
const nameDiff = diffWords(currentName, this._selectedVersion.name);
|
||||
diffs.push({ alias: 'name', diff: nameDiff });
|
||||
|
||||
this.currentVersion.properties.forEach((item) => {
|
||||
const draftValue = draftValues.find((x) => x.alias === item.alias);
|
||||
this._selectedVersion.properties.forEach((item) => {
|
||||
const draftValue = currentPropertyValues.find((x) => x.alias === item.alias);
|
||||
|
||||
if (!draftValue) return;
|
||||
|
||||
const draftValueString = trimQuotes(JSON.stringify(draftValue.value));
|
||||
const versionValueString = trimQuotes(JSON.stringify(item.value));
|
||||
const draftValueString = this.#trimQuotes(JSON.stringify(draftValue.value));
|
||||
const versionValueString = this.#trimQuotes(JSON.stringify(item.value));
|
||||
|
||||
const diff = diffWords(draftValueString, versionValueString);
|
||||
diffs.push({ alias: item.alias, diff });
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* @param str
|
||||
*/
|
||||
function trimQuotes(str: string): string {
|
||||
return str.replace(/^['"]|['"]$/g, '');
|
||||
}
|
||||
this._diffs = [...diffs];
|
||||
}
|
||||
|
||||
#renderSelectedVersion() {
|
||||
if (!this._selectedVersion)
|
||||
return html`
|
||||
<uui-box id="box-right" style="display: flex; align-items: center; justify-content: center;"
|
||||
>No selected version</uui-box
|
||||
>
|
||||
`;
|
||||
|
||||
return html`
|
||||
${unsafeHTML(this.localize.term('rollback_diffHelp'))}
|
||||
<uui-table>
|
||||
<uui-table-column style="width: 0"></uui-table-column>
|
||||
<uui-table-column></uui-table-column>
|
||||
<uui-box headline=${this.currentVersionHeader} id="box-right">
|
||||
${unsafeHTML(this.localize.term('rollback_diffHelp'))}
|
||||
<uui-table>
|
||||
<uui-table-column style="width: 0"></uui-table-column>
|
||||
<uui-table-column></uui-table-column>
|
||||
|
||||
<uui-table-head>
|
||||
<uui-table-head-cell>${this.localize.term('general_alias')}</uui-table-head-cell>
|
||||
<uui-table-head-cell>${this.localize.term('general_value')}</uui-table-head-cell>
|
||||
</uui-table-head>
|
||||
${repeat(
|
||||
diffs,
|
||||
(item) => item.alias,
|
||||
(item) => {
|
||||
const diff = diffs.find((x) => x?.alias === item.alias);
|
||||
return html`
|
||||
<uui-table-row>
|
||||
<uui-table-cell>${item.alias}</uui-table-cell>
|
||||
<uui-table-cell>
|
||||
${diff
|
||||
? diff.diff.map((part) =>
|
||||
part.added
|
||||
? html`<span class="diff-added">${part.value}</span>`
|
||||
: part.removed
|
||||
? html`<span class="diff-removed">${part.value}</span>`
|
||||
: part.value,
|
||||
)
|
||||
: nothing}
|
||||
</uui-table-cell>
|
||||
</uui-table-row>
|
||||
`;
|
||||
},
|
||||
)}
|
||||
</uui-table>
|
||||
<uui-table-head>
|
||||
<uui-table-head-cell>${this.localize.term('general_alias')}</uui-table-head-cell>
|
||||
<uui-table-head-cell>${this.localize.term('general_value')}</uui-table-head-cell>
|
||||
</uui-table-head>
|
||||
${repeat(
|
||||
this._diffs,
|
||||
(item) => item.alias,
|
||||
(item) => {
|
||||
const diff = this._diffs.find((x) => x?.alias === item.alias);
|
||||
return html`
|
||||
<uui-table-row>
|
||||
<uui-table-cell>${item.alias}</uui-table-cell>
|
||||
<uui-table-cell>
|
||||
${diff
|
||||
? diff.diff.map((part) =>
|
||||
part.added
|
||||
? html`<span class="diff-added">${part.value}</span>`
|
||||
: part.removed
|
||||
? html`<span class="diff-removed">${part.value}</span>`
|
||||
: part.value,
|
||||
)
|
||||
: nothing}
|
||||
</uui-table-cell>
|
||||
</uui-table-row>
|
||||
`;
|
||||
},
|
||||
)}
|
||||
</uui-table>
|
||||
</uui-box>
|
||||
`;
|
||||
}
|
||||
|
||||
get currentVersionHeader() {
|
||||
return (
|
||||
this.localize.date(this.currentVersion?.date ?? new Date(), this.#localizeDateOptions) +
|
||||
this.localize.date(this._selectedVersion?.date ?? new Date(), this.#localizeDateOptions) +
|
||||
' - ' +
|
||||
this.currentVersion?.user
|
||||
this._selectedVersion?.user
|
||||
);
|
||||
}
|
||||
|
||||
@@ -302,10 +397,17 @@ export class UmbRollbackModalElement extends UmbModalBaseElement<UmbRollbackModa
|
||||
return html`
|
||||
<umb-body-layout headline="Rollback">
|
||||
<div id="main">
|
||||
<uui-box headline=${this.localize.term('rollback_versions')} id="box-left">
|
||||
<div>${this.#renderVersions()}</div>
|
||||
</uui-box>
|
||||
<uui-box headline=${this.currentVersionHeader} id="box-right"> ${this.#renderCurrentVersion()} </uui-box>
|
||||
<div id="box-left">
|
||||
${this._availableVariants.length
|
||||
? html`
|
||||
<uui-box id="language-box" headline=${this.localize.term('general_language')}>
|
||||
${this.#renderCultureSelect()}
|
||||
</uui-box>
|
||||
`
|
||||
: nothing}
|
||||
${this.#renderVersions()}
|
||||
</div>
|
||||
${this.#renderSelectedVersion()}
|
||||
</div>
|
||||
<umb-footer-layout slot="footer">
|
||||
<uui-button
|
||||
@@ -317,7 +419,8 @@ export class UmbRollbackModalElement extends UmbModalBaseElement<UmbRollbackModa
|
||||
slot="actions"
|
||||
look="primary"
|
||||
@click=${this.#onRollback}
|
||||
label=${this.localize.term('actions_rollback')}></uui-button>
|
||||
label=${this.localize.term('actions_rollback')}
|
||||
?disabled=${!this._selectedVersion}></uui-button>
|
||||
</umb-footer-layout>
|
||||
</umb-body-layout>
|
||||
`;
|
||||
@@ -329,14 +432,15 @@ export class UmbRollbackModalElement extends UmbModalBaseElement<UmbRollbackModa
|
||||
:host {
|
||||
color: var(--uui-color-text);
|
||||
}
|
||||
#language-select {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(--uui-size-space-5);
|
||||
padding-bottom: 0;
|
||||
gap: var(--uui-size-space-2);
|
||||
font-size: 15px;
|
||||
|
||||
#language-box {
|
||||
margin-bottom: var(--uui-size-space-2);
|
||||
}
|
||||
|
||||
#language-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
uui-table {
|
||||
--uui-table-cell-padding: var(--uui-size-space-1) var(--uui-size-space-4);
|
||||
margin-top: var(--uui-size-space-5);
|
||||
@@ -410,15 +514,19 @@ export class UmbRollbackModalElement extends UmbModalBaseElement<UmbRollbackModa
|
||||
.rollback-item uui-button {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#main {
|
||||
display: flex;
|
||||
gap: var(--uui-size-space-4);
|
||||
gap: var(--uui-size-space-5);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#box-left {
|
||||
#versions-box {
|
||||
--uui-box-default-padding: 0;
|
||||
}
|
||||
|
||||
#box-left {
|
||||
max-width: 500px;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
|
||||
Reference in New Issue
Block a user