Skip to content

Commit

Permalink
feat: translate enums in React Material expand panels
Browse files Browse the repository at this point in the history
Expand panels in the React Material UI renderer set show one of the
contained values in their expandable bar. These values are now
translated in case they are enums or oneOf enums.
  • Loading branch information
Maxouwell committed Apr 18, 2024
1 parent 255ee6f commit a672127
Show file tree
Hide file tree
Showing 10 changed files with 947 additions and 39 deletions.
4 changes: 4 additions & 0 deletions packages/core/src/models/uischema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,7 @@ export const isLabelable = (obj: unknown): obj is Labelable =>

export const isLabeled = <T = never>(obj: unknown): obj is Labeled<T> =>
isLabelable(obj) && ['string', 'boolean'].includes(typeof obj.label);

export const isControlElement = (
uiSchema: UISchemaElement
): uiSchema is ControlElement => uiSchema.type === 'Control';
10 changes: 2 additions & 8 deletions packages/core/src/testers/testers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import type {
import {
deriveTypes,
hasType,
isEnumSchema,
isOneOfEnumSchema,
resolveSchema,
} from '../util';
Expand Down Expand Up @@ -369,14 +370,7 @@ export const isOneOfControl = and(
*/
export const isEnumControl = and(
uiTypeIs('Control'),
or(
schemaMatches((schema) =>
Object.prototype.hasOwnProperty.call(schema, 'enum')
),
schemaMatches((schema) =>
Object.prototype.hasOwnProperty.call(schema, 'const')
)
)
schemaMatches((schema) => isEnumSchema(schema))
);

/**
Expand Down
94 changes: 93 additions & 1 deletion packages/core/src/util/label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,28 @@

import startCase from 'lodash/startCase';

import type { ControlElement, JsonSchema, LabelDescription } from '../models';
import {
ControlElement,
JsonSchema,
LabelDescription,
UISchemaElement,
} from '../models';
import { decode } from './path';
import { getI18nKeyPrefix, Translator } from '../i18n';
import { Resolve } from './util';
import {
getFirstPrimitiveProp,
isEnumSchema,
isOneOfEnumSchema,
} from './schema';
import get from 'lodash/get';
import { findUiControl, getPropPath } from './uischema';
import {
EnumOption,
enumToEnumOptionMapper,
oneOfToEnumOptionMapper,
} from './renderer';
import isEqual from 'lodash/isEqual';

const deriveLabel = (
controlElement: ControlElement,
Expand Down Expand Up @@ -81,3 +101,75 @@ const labelDescription = (text: string, show: boolean): LabelDescription => ({
text: text,
show: show,
});

/**
* Compute the child label title for array based controls
* @param data the data of the control
* @param childPath the child path
* @param childLabelProp the dotted path to the value used as child label
* @param {JsonSchema} schema the json schema for this control
* @param {JsonSchema} rootSchema the root json schema
* @param {Translator} translateFct the translator fonction
* @param {UISchemaElement} uiSchema the uiSchema of the control
*/
export const computeChildLabel = (
data: any,
childPath: string,
childLabelProp: string,
schema: JsonSchema,
rootSchema: JsonSchema,
translateFct: Translator,
uiSchema: UISchemaElement
): string => {
const childData = Resolve.data(data, childPath);

if (!childLabelProp) {
childLabelProp = getFirstPrimitiveProp(schema);
}

// return early in case there is no prop we can query
if (!childLabelProp) {
return '';
}

const currentValue = get(childData, childLabelProp, '');

// check whether the value is part of a oneOf or enum and needs to be translated
const childSchema = Resolve.schema(
schema,
'#' + getPropPath(childLabelProp),
rootSchema
);

let enumOption: EnumOption = undefined;
if (isEnumSchema(childSchema)) {
enumOption = enumToEnumOptionMapper(
currentValue,
translateFct,
getI18nKeyPrefix(
childSchema,
findUiControl(uiSchema, childLabelProp),
childPath + '.' + childLabelProp
)
);
} else if (isOneOfEnumSchema(childSchema)) {
const oneOfArray = childSchema.oneOf as JsonSchema[];
const oneOfSchema = oneOfArray.find((e: JsonSchema) =>
isEqual(e.const, currentValue)
);

if (oneOfSchema) {
enumOption = oneOfToEnumOptionMapper(
oneOfSchema,
translateFct,
getI18nKeyPrefix(
oneOfSchema,
undefined,
childPath + '.' + childLabelProp
)
);
}
}

return enumOption ? enumOption.label : currentValue;
};
9 changes: 9 additions & 0 deletions packages/core/src/util/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,12 @@ export const isOneOfEnumSchema = (schema: JsonSchema) =>
Object.prototype.hasOwnProperty.call(schema, 'oneOf') &&
schema.oneOf &&
(schema.oneOf as JsonSchema[]).every((s) => s.const !== undefined);

/**
* Tests whether the schema has an enum.
*/
export const isEnumSchema = (schema: JsonSchema) =>
!!schema &&
typeof schema === 'object' &&
(Object.prototype.hasOwnProperty.call(schema, 'enum') ||
Object.prototype.hasOwnProperty.call(schema, 'const'));
48 changes: 47 additions & 1 deletion packages/core/src/util/uischema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@
*/

import isEmpty from 'lodash/isEmpty';
import { isLayout, UISchemaElement } from '../models';
import {
isControlElement,
isLayout,
isScoped,
UISchemaElement,
} from '../models';
import { encode } from './path';

export type IterateCallback = (uischema: UISchemaElement) => void;

Expand Down Expand Up @@ -55,3 +61,43 @@ export const iterateSchema = (
}
toApply(uischema);
};

/**
* Transform a dotted path to a uiSchema properties path
* @param path a dotted prop path to a schema value (i.e. articles.comment.author)
* @return the uiSchema properties path (i.e. /properties/articles/properties/comment/properties/author)
*/
export const getPropPath = (path: string): string => {
return `/properties/${path
.split('.')
.map((p) => encode(p))
.join('/properties/')}`;
};

/**
* Find a control in a uiSchema, based on the dotted path of the schema value
* @param {UISchemaElement} uiSchema the uiSchema to search from
* @param path a dotted prop path to a schema value (i.e. articles.comment.author)
* @return {UISchemaElement} or undefined if not found
*/
export const findUiControl = (
uiSchema: UISchemaElement,
path: string
): UISchemaElement | undefined => {
if (isControlElement(uiSchema)) {
if (isScoped(uiSchema) && uiSchema.scope.endsWith(getPropPath(path))) {
return uiSchema;
} else if (uiSchema.options?.detail) {
return findUiControl(uiSchema.options.detail, path);
}
}

if (isLayout(uiSchema)) {
for (const elem of uiSchema.elements) {
const result = findUiControl(elem, path);
if (result !== undefined) return result;
}
}

return undefined;
};

0 comments on commit a672127

Please sign in to comment.