Skip to content

Commit

Permalink
fix(preview-middleware-client): dialog and manifest change quick acti…
Browse files Browse the repository at this point in the history
…ons (#2722)

* fix: dialog and manifest change quick actions

* chore: remove unused code

* chore: add changeset

* refactor: remove code duplication

* chore: fix sonar issue

* refactor: don't ignore eslint rule

* chore: update changeset

* chore: fix formatting
  • Loading branch information
voicis authored Dec 23, 2024
1 parent 48d6697 commit d529c38
Show file tree
Hide file tree
Showing 37 changed files with 1,159 additions and 468 deletions.
7 changes: 7 additions & 0 deletions .changeset/modern-phones-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@sap-ux-private/preview-middleware-client': patch
'@sap-ux/control-property-editor': patch
'@sap-ux/preview-middleware': patch
---

Fixed Quick Actions not working after trying to open multiple dialogs and Quick Actions that create manifest changes in SAP Fiori Elements for OData V2 applications not showing correct state when there are unsaved manifest changes.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function NestedQuickActionListItem({
actionIndex
}: Readonly<NestedQuickActionListItemProps>): ReactElement {
const dispatch = useDispatch();
const isDisabled = useSelector<RootState, boolean>((state) => state.appMode === 'navigation');
const isDisabled = useSelector<RootState, boolean>((state) => state.appMode === 'navigation') || !action.enabled;
const [showContextualMenu, setShowContextualMenu] = useState(false);
const [target, setTarget] = useState<(EventTarget & (HTMLAnchorElement | HTMLElement | HTMLButtonElement)) | null>(
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ export default class AddFragment extends BaseDialog<AddFragmentModel> {
title: options.title,
completeView: options.aggregation === undefined
});
this.ui5Version = sap.ui.version;
this.commandExecutor = new CommandExecutor(this.rta);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ export default class AddTableColumnFragments extends BaseDialog<AddTableColumnsF
this.model = new JSONModel({
title: options.title
}) as AddTableColumnsFragmentsModel;
this.ui5Version = sap.ui.version;
this.commandExecutor = new CommandExecutor(this.rta);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,6 @@ export default abstract class BaseDialog<T extends BaseDialogModel = BaseDialogM
* Dialog instance
*/
public dialog: Dialog;
/**
* UI5 version
*/
public ui5Version: string;
/**
* RTA Command Executor
*/
Expand Down
134 changes: 134 additions & 0 deletions packages/preview-middleware-client/src/adp/dialog-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import Dialog from 'sap/m/Dialog';
import UI5Element from 'sap/ui/core/Element';
import Fragment from 'sap/ui/core/Fragment';
import RuntimeAuthoring from 'sap/ui/rta/RuntimeAuthoring';

import { getTextBundle } from '../i18n';

import AddFragment, { AddFragmentOptions } from './controllers/AddFragment.controller';
import AddTableColumnFragments from './controllers/AddTableColumnFragments.controller';
import ControllerExtension from './controllers/ControllerExtension.controller';
import ExtensionPoint from './controllers/ExtensionPoint.controller';

import { ExtensionPointData } from './extension-point';

export const enum DialogNames {
ADD_FRAGMENT = 'AddFragment',
ADD_TABLE_COLUMN_FRAGMENTS = 'AddTableColumnFragments',
CONTROLLER_EXTENSION = 'ControllerExtension',
ADD_FRAGMENT_AT_EXTENSION_POINT = 'ExtensionPoint'
}

type Controller = AddFragment | AddTableColumnFragments | ControllerExtension | ExtensionPoint;

export const OPEN_DIALOG_STATUS_CHANGED = 'OPEN_DIALOG_STATUS_CHANGED';

export class DialogFactory {
private static readonly eventTarget = new EventTarget();
private static isDialogOpen = false;
/**
* Only one dialog can be open at a time. This flag indicates if a new dialog can be opened.
*/

public static get canOpenDialog(): boolean {
return !this.isDialogOpen;
}

/**
* Factory method for creating a new dialog.
*
* @param overlay - Control overlay.
* @param rta - Runtime Authoring instance.
* @param dialogName - Dialog name.
* @param extensionPointData - Control ID.
* @param options - Dialog options.
*/
public static async createDialog(
overlay: UI5Element,
rta: RuntimeAuthoring,
dialogName: DialogNames,
extensionPointData?: ExtensionPointData,
options: Partial<AddFragmentOptions> = {}
): Promise<void> {
if (this.isDialogOpen) {
return;
}
let controller: Controller;
const resources = await getTextBundle();

switch (dialogName) {
case DialogNames.ADD_FRAGMENT:
controller = new AddFragment(`open.ux.preview.client.adp.controllers.${dialogName}`, overlay, rta, {
aggregation: options.aggregation,
title: resources.getText(options.title ?? 'ADP_ADD_FRAGMENT_DIALOG_TITLE')
});
break;
case DialogNames.ADD_TABLE_COLUMN_FRAGMENTS:
controller = new AddTableColumnFragments(
`open.ux.preview.client.adp.controllers.${dialogName}`,
overlay,
rta,
{
aggregation: options.aggregation,
title: resources.getText(options.title ?? 'ADP_ADD_FRAGMENT_DIALOG_TITLE')
}
);
break;
case DialogNames.CONTROLLER_EXTENSION:
controller = new ControllerExtension(
`open.ux.preview.client.adp.controllers.${dialogName}`,
overlay,
rta
);
break;
case DialogNames.ADD_FRAGMENT_AT_EXTENSION_POINT:
controller = new ExtensionPoint(
`open.ux.preview.client.adp.controllers.${dialogName}`,
overlay,
rta,
extensionPointData!
);
break;
}

const id = dialogName === DialogNames.ADD_FRAGMENT_AT_EXTENSION_POINT ? `dialog--${dialogName}` : undefined;

const dialog = (await Fragment.load({
name: `open.ux.preview.client.adp.ui.${dialogName}`,
controller,
id
})) as Dialog;

this.isDialogOpen = true;
dialog.attachBeforeClose(() => {
this.updateStatus(false);
});

await controller.setup(dialog);
this.updateStatus(true);
}

/**
* Updates open dialog status.
*
* @param isDialogOpen Flag indicating if there is an open dialog.
*/
private static updateStatus(isDialogOpen: boolean) {
this.isDialogOpen = isDialogOpen;
const event = new CustomEvent(OPEN_DIALOG_STATUS_CHANGED);
this.eventTarget.dispatchEvent(event);
}

/**
* Attach event handler for OPEN_DIALOG_STATUS_CHANGED event.
*
* @param handler Event handler.
* @returns Function that removes listener.
*/
public static onOpenDialogStatusChange(handler: (event: CustomEvent) => void | Promise<void>): () => void {
this.eventTarget.addEventListener(OPEN_DIALOG_STATUS_CHANGED, handler as EventListener);
return () => {
this.eventTarget.removeEventListener(OPEN_DIALOG_STATUS_CHANGED, handler as EventListener);
};
}
}
4 changes: 2 additions & 2 deletions packages/preview-middleware-client/src/adp/extension-point.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import CommandFactory from 'sap/ui/rta/command/CommandFactory';
import { ExternalAction, addExtensionPoint } from '@sap-ux-private/control-property-editor-common';

import { Deferred, createDeferred } from './utils';
import { DialogFactory, DialogNames } from './dialog-factory';

import { CommunicationService } from '../cpe/communication-service';
import { DialogNames, handler } from './init-dialogs';

type ActionService = {
execute: (controlId: string, actionId: string) => void;
Expand Down Expand Up @@ -100,7 +100,7 @@ export default class ExtensionPointService {
let deferred = createDeferred<DeferredExtPointData>();
const name = this.selectedExtensionPointName;

await handler(overlay, this.rta, DialogNames.ADD_FRAGMENT_AT_EXTENSION_POINT, {
await DialogFactory.createDialog(overlay, this.rta, DialogNames.ADD_FRAGMENT_AT_EXTENSION_POINT, {
name,
info,
deferred
Expand Down
82 changes: 5 additions & 77 deletions packages/preview-middleware-client/src/adp/init-dialogs.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
/** sap.m */
import type Dialog from 'sap/m/Dialog';

/** sap.ui.core */
import Fragment from 'sap/ui/core/Fragment';
import UI5Element from 'sap/ui/core/Element';

/** sap.ui.rta */
Expand All @@ -15,24 +11,10 @@ import FlUtils from 'sap/ui/fl/Utils';
/** sap.ui.dt */
import type ElementOverlay from 'sap/ui/dt/ElementOverlay';

import AddFragment, { AddFragmentOptions } from './controllers/AddFragment.controller';
import ControllerExtension from './controllers/ControllerExtension.controller';
import { ExtensionPointData } from './extension-point';
import ExtensionPoint from './controllers/ExtensionPoint.controller';
import ManagedObject from 'sap/ui/base/ManagedObject';
import { isReuseComponent } from '../cpe/utils';
import { Ui5VersionInfo } from '../utils/version';
import { getTextBundle } from '../i18n';
import AddTableColumnFragments from './controllers/AddTableColumnFragments.controller';

export const enum DialogNames {
ADD_FRAGMENT = 'AddFragment',
ADD_TABLE_COLUMN_FRAGMENTS = 'AddTableColumnFragments',
CONTROLLER_EXTENSION = 'ControllerExtension',
ADD_FRAGMENT_AT_EXTENSION_POINT = 'ExtensionPoint'
}

type Controller = AddFragment | AddTableColumnFragments | ControllerExtension | ExtensionPoint;
import { DialogFactory, DialogNames } from './dialog-factory';

/**
* Handler for enablement of Extend With Controller context menu entry
Expand Down Expand Up @@ -105,62 +87,6 @@ export const getAddFragmentItemText = (overlay: ElementOverlay) => {
return 'Add: Fragment';
};

/**
* Handler for new context menu entry
*
* @param overlay Control overlays
* @param rta Runtime Authoring
* @param dialogName Dialog name
* @param extensionPointData Control ID
* @param options Dialog options
*/
export async function handler(
overlay: UI5Element,
rta: RuntimeAuthoring,
dialogName: DialogNames,
extensionPointData?: ExtensionPointData,
options: Partial<AddFragmentOptions> = {}
): Promise<void> {
let controller: Controller;
const resources = await getTextBundle();

switch (dialogName) {
case DialogNames.ADD_FRAGMENT:
controller = new AddFragment(`open.ux.preview.client.adp.controllers.${dialogName}`, overlay, rta, {
aggregation: options.aggregation,
title: resources.getText(options.title ?? 'ADP_ADD_FRAGMENT_DIALOG_TITLE')
});
break;
case DialogNames.ADD_TABLE_COLUMN_FRAGMENTS:
controller = new AddTableColumnFragments(`open.ux.preview.client.adp.controllers.${dialogName}`, overlay, rta, {
aggregation: options.aggregation,
title: resources.getText(options.title ?? 'ADP_ADD_FRAGMENT_DIALOG_TITLE')
});
break;
case DialogNames.CONTROLLER_EXTENSION:
controller = new ControllerExtension(`open.ux.preview.client.adp.controllers.${dialogName}`, overlay, rta);
break;
case DialogNames.ADD_FRAGMENT_AT_EXTENSION_POINT:
controller = new ExtensionPoint(
`open.ux.preview.client.adp.controllers.${dialogName}`,
overlay,
rta,
extensionPointData!
);
break;
}

const id = dialogName === DialogNames.ADD_FRAGMENT_AT_EXTENSION_POINT ? `dialog--${dialogName}` : undefined;

const dialog = await Fragment.load({
name: `open.ux.preview.client.adp.ui.${dialogName}`,
controller,
id
});

await controller.setup(dialog as Dialog);
}

/**
* Adds a new item to the context menu
*
Expand All @@ -174,15 +100,17 @@ export const initDialogs = (rta: RuntimeAuthoring, syncViewsIds: string[], ui5Ve
contextMenu.addMenuItem({
id: 'ADD_FRAGMENT',
text: getAddFragmentItemText,
handler: async (overlays: UI5Element[]) => await handler(overlays[0], rta, DialogNames.ADD_FRAGMENT),
handler: async (overlays: UI5Element[]) =>
await DialogFactory.createDialog(overlays[0], rta, DialogNames.ADD_FRAGMENT),
icon: 'sap-icon://attachment-html',
enabled: (overlays: ElementOverlay[]) => isFragmentCommandEnabled(overlays, ui5VersionInfo)
});

contextMenu.addMenuItem({
id: 'EXTEND_CONTROLLER',
text: 'Extend With Controller',
handler: async (overlays: UI5Element[]) => await handler(overlays[0], rta, DialogNames.CONTROLLER_EXTENSION),
handler: async (overlays: UI5Element[]) =>
await DialogFactory.createDialog(overlays[0], rta, DialogNames.CONTROLLER_EXTENSION),
icon: 'sap-icon://create-form',
enabled: (overlays: ElementOverlay[]) => isControllerExtensionEnabled(overlays, syncViewsIds, ui5VersionInfo)
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import type {
QuickActionContext,
SimpleQuickActionDefinition
} from '../../../cpe/quick-actions/quick-action-definition';
import { DialogNames, handler, isControllerExtensionEnabledForControl } from '../../init-dialogs';
import { DialogFactory, DialogNames } from '../../dialog-factory';
import { isControllerExtensionEnabledForControl } from '../../init-dialogs';
import { getExistingController } from '../../api-handler';
import { SimpleQuickActionDefinitionBase } from '../simple-quick-action-base';
import { DIALOG_ENABLEMENT_VALIDATOR } from '../dialog-enablement-validator';

export const ADD_CONTROLLER_TO_PAGE_TYPE = 'add-controller-to-page';
const CONTROL_TYPES = ['sap.f.DynamicPage', 'sap.uxap.ObjectPageLayout'];
Expand All @@ -23,7 +25,7 @@ export class AddControllerToPageQuickAction
implements SimpleQuickActionDefinition
{
constructor(context: QuickActionContext) {
super(ADD_CONTROLLER_TO_PAGE_TYPE, CONTROL_TYPES, '', context);
super(ADD_CONTROLLER_TO_PAGE_TYPE, CONTROL_TYPES, '', context, [DIALOG_ENABLEMENT_VALIDATOR]);
}

private controllerExists = false;
Expand Down Expand Up @@ -52,7 +54,7 @@ export class AddControllerToPageQuickAction
async execute(): Promise<FlexCommand[]> {
if (this.control) {
const overlay = OverlayRegistry.getOverlay(this.control) || [];
await handler(overlay, this.context.rta, DialogNames.CONTROLLER_EXTENSION);
await DialogFactory.createDialog(overlay, this.context.rta, DialogNames.CONTROLLER_EXTENSION);
}
return [];
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import OverlayRegistry from 'sap/ui/dt/OverlayRegistry';
import FlexCommand from 'sap/ui/rta/command/FlexCommand';

import { DialogNames, handler } from '../../init-dialogs';
import { DialogFactory, DialogNames } from '../../dialog-factory';
import { QuickActionContext, SimpleQuickActionDefinition } from '../../../cpe/quick-actions/quick-action-definition';
import { SimpleQuickActionDefinitionBase } from '../simple-quick-action-base';
import { getApplicationType } from '../../../utils/application';
import { getUi5Version, isLowerThanMinimalUi5Version } from '../../../utils/version';
import { DIALOG_ENABLEMENT_VALIDATOR } from '../dialog-enablement-validator';

export const ADD_PAGE_ACTION = 'add-page-action';
const CONTROL_TYPES = ['sap.f.DynamicPageTitle', 'sap.uxap.ObjectPageHeader', 'sap.uxap.ObjectPageDynamicHeaderTitle'];
Expand All @@ -15,7 +16,9 @@ const CONTROL_TYPES = ['sap.f.DynamicPageTitle', 'sap.uxap.ObjectPageHeader', 's
*/
export class AddPageActionQuickAction extends SimpleQuickActionDefinitionBase implements SimpleQuickActionDefinition {
constructor(context: QuickActionContext) {
super(ADD_PAGE_ACTION, CONTROL_TYPES, 'QUICK_ACTION_ADD_CUSTOM_PAGE_ACTION', context);
super(ADD_PAGE_ACTION, CONTROL_TYPES, 'QUICK_ACTION_ADD_CUSTOM_PAGE_ACTION', context, [
DIALOG_ENABLEMENT_VALIDATOR
]);
}

async initialize(): Promise<void> {
Expand All @@ -24,13 +27,13 @@ export class AddPageActionQuickAction extends SimpleQuickActionDefinitionBase im
if (appType === 'fe-v4' && isLowerThanMinimalUi5Version(version, { major: 1, minor: 130 })) {
return;
}
return super.initialize();
super.initialize();
}

async execute(): Promise<FlexCommand[]> {
if (this.control) {
const overlay = OverlayRegistry.getOverlay(this.control) || [];
await handler(overlay, this.context.rta, DialogNames.ADD_FRAGMENT, undefined, {
await DialogFactory.createDialog(overlay, this.context.rta, DialogNames.ADD_FRAGMENT, undefined, {
aggregation: 'actions',
title: 'QUICK_ACTION_ADD_CUSTOM_PAGE_ACTION'
});
Expand Down
Loading

0 comments on commit d529c38

Please sign in to comment.