Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display and write appDescr changes for v4 #2458

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/control-property-editor-common/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface Control {
properties: ControlProperty[];
}
export type PropertyValue = string | boolean | number;
export type PropertyChangeType = 'propertyChange' | 'propertyBindingChange';
export type PropertyChangeType = 'propertyChange' | 'propertyBindingChange' | 'appdescr_fe_changePageConfiguration';
export interface PropertyChange<T extends PropertyValue = PropertyValue> {
controlId: string;
controlName: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { Link, Stack } from '@fluentui/react';

import { useAppDispatch } from '../../store';
import type { Change } from '@sap-ux-private/control-property-editor-common';
import { PROPERTY_CHANGE_KIND, SAVED_CHANGE_TYPE, selectControl } from '@sap-ux-private/control-property-editor-common';
import {
PROPERTY_CHANGE_KIND,
SAVED_CHANGE_TYPE,
selectControl
} from '@sap-ux-private/control-property-editor-common';

import { PropertyChange } from './PropertyChange';
import { OtherChange } from './OtherChange';
Expand Down
91 changes: 70 additions & 21 deletions packages/preview-middleware-client/src/cpe/changes/flex-change.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ import CommandFactory from 'sap/ui/rta/command/CommandFactory';
import type { PropertyChange } from '@sap-ux-private/control-property-editor-common';
import type { UI5AdaptationOptions } from '../types';
import { validateBindingModel } from './validator';
import ManagedObject from 'sap/ui/base/ManagedObject';
import OverlayRegistry from 'sap/ui/dt/OverlayRegistry';
import ElementOverlay from 'sap/ui/dt/ElementOverlay';
import UI5Element from 'sap/ui/core/Element';
import OverlayUtil from 'sap/ui/dt/OverlayUtil';
import BaseCommand from 'sap/ui/rta/command/BaseCommand';
import { getReference } from '../../utils/application';

/**
* Function to check a give value is a binding expression.
Expand All @@ -17,39 +24,81 @@ function isBindingExpression(value: string): boolean {
/**
*
* @param options UI5 adaptation options
* @param change changed property of a control
* @param change changed property/app descriptor property of a control
*/
export async function applyChange(options: UI5AdaptationOptions, change: PropertyChange): Promise<void> {
const { rta } = options;
const modifiedControl = sap.ui.getCore().byId(change.controlId);
if (!modifiedControl) {
return;
}

let command: BaseCommand;
const isBindingString = typeof change.value === 'string' && isBindingExpression(change.value);
const modifiedControlModifiedProperties = modifiedControl.getMetadata().getAllProperties()[change.propertyName];
const isBindingModel = isBindingString && modifiedControlModifiedProperties?.type === 'string';
const flexSettings = rta.getFlexSettings();
const changeType = isBindingString ? 'BindProperty' : 'Property';
const overlay = getOverlay(modifiedControl);
const overlayData = overlay?.getDesignTimeMetadata().getData();
if (modifiedControlModifiedProperties && !overlayData?.properties?.[change.propertyName]?.ignore) {
const isBindingModel = isBindingString && modifiedControlModifiedProperties?.type === 'string';
const changeType = isBindingString ? 'BindProperty' : 'Property';

if (isBindingModel) {
validateBindingModel(modifiedControl, change.value as string);
}
if (isBindingModel) {
validateBindingModel(modifiedControl, change.value as string);
}

const property = isBindingString ? 'newBinding' : 'newValue';
const modifiedValue = {
generator: flexSettings.generator,
propertyName: change.propertyName,
[property]: change.value
};

const property = isBindingString ? 'newBinding' : 'newValue';
const modifiedValue = {
generator: flexSettings.generator,
propertyName: change.propertyName,
[property]: change.value
};

const command = await CommandFactory.getCommandFor<FlexCommand>(
modifiedControl,
changeType,
modifiedValue,
null,
flexSettings
);
command = await CommandFactory.getCommandFor<FlexCommand>(
modifiedControl,
changeType,
modifiedValue,
null,
flexSettings
);
} else {
if (!overlay) return;
const overlayData = overlay?.getDesignTimeMetadata().getData();
const manifestPropertyPath = overlayData.manifestPropertyPath(modifiedControl);
const [manifestPropertyChange] = overlayData.manifestPropertyChange(
{ [change.propertyName]: change.value },
manifestPropertyPath,
modifiedControl
);

const modifiedValue = {
reference: getReference(modifiedControl),
appComponent: manifestPropertyChange.appComponent,
changeType: manifestPropertyChange.changeSpecificData.appDescriptorChangeType, // 'appdescr_fe_changePageConfiguration',
parameters: manifestPropertyChange.changeSpecificData.content.parameters,
selector: manifestPropertyChange.selector
};

// TODO> confirm whether confirmmposite command requuired
// implementation missing for undo for appDedc command
// const compositeCommand = await CommandFactory.getCommandFor(modifiedControl, 'composite');
command = await CommandFactory.getCommandFor(
modifiedControl,
'appDescriptor',
modifiedValue,
null,
flexSettings
);
}

await rta.getCommandStack().pushAndExecute(command);
}

export const getOverlay = (control: UI5Element): ElementOverlay | undefined => {
let controlOverlay = OverlayRegistry.getOverlay(control);
if (!controlOverlay?.getDomRef()) {
//look for closest control
controlOverlay = OverlayUtil.getClosestOverlayFor(control);
}

return controlOverlay;
};
141 changes: 111 additions & 30 deletions packages/preview-middleware-client/src/cpe/changes/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,46 @@ interface ChangeContent {
newBinding: string;
}

interface ManifestChangeContent {
page: string;
entityPropertyChange: {
propertyPath: string;
operation: 'UPSERT' | 'DELETE' | 'INSERT' | 'UPDATE';
propertyValue: string;
};
}

interface ChangeSelector {
id: string;
type: string;
}

interface Change {
interface BaseChange {
fileName: string;
controlId: string;
propertyName: string;
value: string;
timestamp: string;
creation: string;
value: string;
selector: ChangeSelector;
}

const PROPRTY_CHANGE = 'propertyChange';
const PROPRTY_BINDING_CHANGE = 'propertyBindingChange';
const MANIFEST_V4_CHANGE = 'appdescr_fe_changePageConfiguration';

interface Change extends BaseChange {
changeType: typeof PROPRTY_CHANGE | typeof PROPRTY_BINDING_CHANGE;
controlId: string;
propertyName: string;
content: ChangeContent;
creation: string;
changeType: string;
}

type SavedChangesResponse = Record<string, Change>;
interface ManifestChange extends BaseChange {
changeType: typeof MANIFEST_V4_CHANGE;
propertyName: string;
content: ManifestChangeContent;
}

type SavedChangesResponse = Record<string, Change | ManifestChange>;

type Properties<T extends object> = { [K in keyof T]-?: K extends string ? K : never }[keyof T];
/**
Expand Down Expand Up @@ -78,6 +100,17 @@ function assertChange(change: Change): void {
assertProperties(['property'], change.content);
}

/**
* Assert v4 manifest change for its validity. Throws error if no value found in saved changes.
*
* @param change Change object
*/
function assertManifestChange(change: ManifestChange): void {
assertProperties(['fileName', 'content', 'creation'], change);
assertProperties(['page', 'entityPropertyChange'], change.content);
assertProperties(['propertyPath', 'operation', 'propertyValue'], change.content.entityPropertyChange);
}

/**
* Modify rta message.
*
Expand Down Expand Up @@ -177,30 +210,63 @@ export class ChangeService {
const changes = (
Object.keys(savedChanges ?? {})
.map((key): SavedPropertyChange | UnknownSavedChange | undefined => {
const change: Change = savedChanges[key];
const change: Change | ManifestChange = savedChanges[key];
try {
assertChange(change);
if (
[change.content.newValue, change.content.newBinding].every(
(item) => item === undefined || item === null
)
) {
throw new Error('Invalid change, missing new value in the change file');
}
if (change.changeType !== 'propertyChange' && change.changeType !== 'propertyBindingChange') {
throw new Error('Unknown Change Type');
if (change.changeType === 'appdescr_fe_changePageConfiguration') {
assertManifestChange(change);
if (
[
change.content.page,
change.content.entityPropertyChange,
change.content.entityPropertyChange.operation,
change.content.entityPropertyChange.propertyPath,
change.content.entityPropertyChange.propertyValue
].every((item) => item === undefined || item === null)
) {
throw new Error('Invalid change, missing property path or property value on change file');
}
return {
type: 'saved',
kind: 'property',
fileName: change.fileName,
controlId: change.selector.id,
propertyName: change.content.entityPropertyChange.propertyPath.split('/').pop() ?? '',
value: change.content.entityPropertyChange.propertyValue,
timestamp: new Date(change.creation).getTime(),
controlName: change.selector.type
? (change.selector.type.split('.').pop() as string)
: '',
changeType: change.changeType
};
} else {
assertChange(change);
if (
[change.content.newValue, change.content.newBinding].every(
(item) => item === undefined || item === null
)
) {
throw new Error('Invalid change, missing new value in the change file');
}
if (
change.changeType !== 'propertyChange' &&
change.changeType !== 'propertyBindingChange'
) {
throw new Error('Unknown Change Type');
}
return {
type: 'saved',
kind: 'property',
fileName: change.fileName,
controlId: change.selector.id,
propertyName: change.content.property,
value: change.content.newValue ?? change.content.newBinding,
timestamp: new Date(change.creation).getTime(),
controlName: change.selector.type
? (change.selector.type.split('.').pop() as string)
: '',
changeType: change.changeType
};
}
return {
type: 'saved',
kind: 'property',
fileName: change.fileName,
controlId: change.selector.id,
propertyName: change.content.property,
value: change.content.newValue ?? change.content.newBinding,
timestamp: new Date(change.creation).getTime(),
controlName: change.selector.type ? (change.selector.type.split('.').pop() as string) : '',
changeType: change.changeType
};
} catch (error) {
// Gracefully handle change files with invalid content
if (change.fileName) {
Expand All @@ -209,7 +275,7 @@ export class ChangeService {
kind: 'unknown',
changeType: change.changeType,
fileName: change.fileName,
controlId: change.selector?.id // some changes may not have selector
controlId: change?.selector?.id // some changes may not have selector
};
if (change.creation) {
unknownChange.timestamp = new Date(change.creation).getTime();
Expand Down Expand Up @@ -332,6 +398,9 @@ export class ChangeService {
case 'propertyBindingChange':
value = command.getProperty('newBinding') as string;
break;
case 'appdescr_fe_changePageConfiguration':
value = command.getProperty('parameters').entityPropertyChange.propertyValue as string; //entityPropertyChange.propertyValue as string;
break;
}
const { fileName } = command.getPreparedChange().getDefinition();
if (changeType === 'propertyChange' || changeType === 'propertyBindingChange') {
Expand All @@ -346,6 +415,18 @@ export class ChangeService {
controlName: command.getElement().getMetadata().getName().split('.').pop() ?? '',
fileName
};
} else if (changeType === 'appdescr_fe_changePageConfiguration') {
result = {
type: 'pending',
kind: 'property',
controlId: selectorId,
changeType,
propertyName: command.getProperty('parameters').entityPropertyChange.propertyPath.split('/').pop(),
isActive: index >= inactiveCommandCount,
value,
controlName: command.getElement().getMetadata().getName().split('.').pop() ?? '',
fileName
};
} else {
result = {
type: 'pending',
Expand Down
Loading
Loading